Skip to content

Instantly share code, notes, and snippets.

@gaplo917
Created September 16, 2020 11:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gaplo917/04f63e0f01ecd751853550a4b78df994 to your computer and use it in GitHub Desktop.
Save gaplo917/04f63e0f01ecd751853550a4b78df994 to your computer and use it in GitHub Desktop.
Web Worker RPC Impl
export const status = {
OK: 0,
CANCELLED: 1,
INVALID_ARGUMENT: 3,
UNIMPLEMENTED: 12,
INTERNAL: 13,
}
/**
*
* @param self Web Worker Scope
* @param methods RPC Methods
*/
export function wRPCServer(self, methods) {
self.addEventListener('message', function ({ data }) {
let id = data.id
if (data.type !== 'wRPC' || id == null) return
if (data.method) {
let method = methods[data.method]
if (method == null) {
self.postMessage(
{
type: 'wRPC',
id,
error: 'WRPC_STATUS_UNIMPLEMENTED',
status: status.UNIMPLEMENTED,
},
null,
)
} else {
Promise.resolve()
.then(() => method(...data.params))
.then(result => {
self.postMessage({ type: 'wRPC', id, result, status: status.OK }, null)
})
.catch(err => {
self.postMessage({ type: 'wRPC', id, error: '' + err, status: status.INTERNAL }, null)
})
}
} else {
self.postMessage(
{
type: 'wRPC',
id,
error: 'WRPC_STATUS_INVALID_ARGUMENT, invalid `method` argument ',
status: status.INVALID_ARGUMENT,
},
null,
)
}
})
}
const handleRPCCallbacks = (event, callbacks) => {
if (event.data && event.data.type === 'wRPC') {
const cbTuple = callbacks.get(event.data.id)
// callback must fulfil the tuple structure [resolve, reject]
if (cbTuple && typeof cbTuple[0] === 'function' && typeof cbTuple[1] === 'function') {
if (event.data.status === status.OK) {
cbTuple[0](event.data.result)
} else if (event.data.status > status.OK) {
cbTuple[1](event.data.error)
}
}
// remove the callbacks
callbacks.delete(event.data.id)
}
}
/**
* rpc call that return standard promise
* @param worker Worker
* @returns { * | Worker }
*/
export function wRPC(worker) {
const callbacks = new Map()
let counter = 0
const call = method => (...params) => {
return new Promise((resolve, reject) => {
const id = ++counter
callbacks.set(id, [resolve, reject])
worker.postMessage({ type: 'wRPC', id, method, params })
})
}
worker.onmessage = event => handleRPCCallbacks(event, callbacks)
return new Proxy(worker, {
get(target, p, receiver) {
return call(p)
},
})
}
/**
* rpc call that return react suspense compatible contract
* @param worker Worker
* @returns { * | Worker }
*/
export function wRPCSuspense(worker) {
const callbacks = new Map()
let counter = 0
const call = method => (...params) => {
let status = 'pending'
let result = null
const suspender = new Promise((resolve, reject) => {
const id = ++counter
callbacks.set(id, [
r => {
status = 'success'
result = r
resolve()
},
err => {
status = 'error'
result = err
reject()
},
])
worker.postMessage({ type: 'wRPC', id, method, params })
})
return () => {
if (status === 'pending') {
throw suspender
} else if (status === 'error') {
throw result
} else if (status === 'success') {
return result
}
}
}
worker.onmessage = event => handleRPCCallbacks(event, callbacks)
return new Proxy(worker, {
get(target, p, receiver) {
return call(p)
},
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment