Async Call
async-call-rpc
is a JSON RPC server and client written in TypeScript for any ES6+ environment.

Links
CHANGELOG.md | Document of AsyncCall | Document of AsyncGeneratorCall | Playground
Chapters:
Features
- Zero dependencies!
- Running in any ES6+ environment (+
globalThis
), no requirement on any Web or Node API - Simple to define a server and simple to use as a client
- Full TypeScript support
- Support custom serializer to pass complex data types
- Support async generator (Require both server and client supports 4 JSON RPC internal methods, and
Symbol.asyncIterator
, (async function* () {}).constructor.prototype
available)
Cautions
- NOT support ECMAScript 5 (ES6
Proxy
is the core of this library) - This package is shipping ECMAScript 2018 syntax (including
async function
). - The async generator mode might leak memory on the server. Use it by your caution.
- NOT support JSON RPC 1.0
The first concept: channel
The channel
is the only thing you need to learn to use this library.
This library is designed to not rely on any specific platform. Only require things defined in the ECMAScript specification.
In the ES spec, there is no I/O related API so it's impossible to communicate with the outer world.
You need to implement one of the following interfaces:
There are some built-in channel you can simplify the usage.
The following document will assume you have defined your channel
.
Example
Server example
export function add(x: number, y: number) {
return x + y
}
export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
import { AsyncCall } from 'async-call-rpc'
import * as server from './server'
AsyncCall(server, { channel })
Client example
import { AsyncCall } from 'async-call-rpc'
const server = AsyncCall<typeof server>({}, { channel })
server.add(2, 40).then(console.log)
Isomorphic API
You can notice from the above example,
define a server is using AsyncCall(serverImplementation, opt)
,
define a client is using AsyncCall<typeof serverImplementation>({}, opt)
.
So it is possible to define a server and a client at the same time.
Notifications and Batch requests
AsyncCall can send Notifications.
Using notifications means results or remote errors will be dropped. Local errors won't be omitted, e.g. serializer error or network error.
import { AsyncCall, notify } from 'async-call-rpc'
const server = notify(AsyncCall<typeof server>({}, { channel }))
server.online().then(console.log)
AsyncCall can send batch request too.
import { AsyncCall, batch } from 'async-call-rpc'
const [server, emit, drop] = batch(AsyncCall<typeof server>({}, { channel }))
const a = server.req1()
const b = server.req2()
const c = server.req3()
emit()
const d = server.req1()
drop()
Installation
Install through npm
npm i async-call-rpc
yarn add async-call-rpc
Import from browser or Deno
You can access https://www.jsdelivr.com/package/npm/async-call-rpc?path=out to get the latest URL and SRI.
(Supports type definition for deno out-of-box!)
import { AsyncCall } from 'https://cdn.jsdelivr.net/npm/async-call-rpc@latest/out/base.mjs'
UMD
<script src="https://cdn.jsdelivr.net/npm/async-call-rpc@2.0.1/out/base.js"></script>
<script>
const { AsyncCall } = globalThis.AsyncCall
</script>
In other JS environment
Load the out/base.mjs
(ES Module) or out/base.js
(UMD, CommonJS or AMD) to your project.
Entries
This library has 2 entry. base
and full
. base
is the default entry point. The full
version includes the AsyncGeneratorCall
but the base version doesn't.
Browser / Deno
Please check out https://www.jsdelivr.com/package/npm/async-call-rpc?path=out
Node:
require('async-rpc-call/full')
import * as RPC from 'async-rpc-call/full'
require('async-rpc-call/base')
import * as RPC from 'async-rpc-call/base'
Builtin channels
They're not part of the core library but provided as utils to increase usability.
(Node) WebSocket
(Deno) WebSocket
(Web) WebSocket
⚠️ Broadcast Channel is not supported by Safari yet ⚠️
| Server & Client |
---|
Entry point | https://cdn.jsdelivr.net/npm/async-call-rpc@latest/utils/web/broadcast.channel.js (Source code) |
Entry point type | ES Module |
Dependencies | Nothing |
Example | TBD |
⚠️ Import a ES Module in a Web Worker is only supported by Chrome yet! ⚠️
Main frame: new WorkerChannel(new Worker(...))
Worker: new WorkerChannel()
Builtin serializers
(Web, Deno and Node) BSON
| Server |
---|
Entry point Node | async-call-rpc/utils/node/bson.js (Source code) |
Entry point Browser/Deno | https://cdn.jsdelivr.net/npm/async-call-rpc@latest/utils/web/bson.js (Source code) |
Dependencies | bson |
(Web, Deno and Node) Msgpack
Utils available if both server and client are created by this library
AsyncCall has some non-standard extensions to the JSON RPC specification that can help the library easier to use. Those features aren't enabled by default.
Implemented JSON RPC internal methods
These four methods are used to implement AsyncGeneratorCall
support.
interface JSONRPC_Internal_Methods {
'rpc.async-iterator.start'(method: string, params: unknown[]): Promise<string>
'rpc.async-iterator.next'(id: string, value: unknown): Promise<IteratorResult<unknown>>
'rpc.async-iterator.return'(id: string, value: unknown): Promise<IteratorResult<unknown>>
'rpc.async-iterator.throw'(id: string, value: unknown): Promise<IteratorResult<unknown>>
}
Non-standard extension to JSON RPC specification
remoteStack on Request object
This library can send the client the call stack to the server to make the logger better.
Controlled by option.log.sendLocalStack
. Default to false
.
interface JSONRPC_Request_object {
remoteStack?: string
}
"undef" on Response object
This is a non-standard property appears when using JSONSerialization due to JSON doesn't support undefined
. It's a hint to the client, that the result is undefined
.
This behavior is controlled by the 3rd parameter of JSONSerialization(replacerAndReceiver?, space?, undefinedKeepingBehavior?: false | "keep" | "null" = "null"). Default to "null"
. To turn on this feature to "keep" undefined values, change the 3rd option to "keep".
interface JSONRPC_Response_object {
undef?: boolean
}
The implementation-defined Error data
In the JSON RPC specification, this is implementation-defined. This is controlled by the option options.mapError
This library will try to "Recover" the Error object if there is enough information from another side.
interface JSONRPC_Error_object {
data?: {
stack?: string
type?:
| string
| 'Error'
| 'EvalError'
| 'RangeError'
| 'ReferenceError'
| 'SyntaxError'
| 'TypeError'
| 'URIError'
| 'DOMException'
}
}