Skip to content

Instantly share code, notes, and snippets.

@antimatter15
Created December 27, 2017 09:17
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 antimatter15/a04c19eb43ee94236273e2a43f8fbcc8 to your computer and use it in GitHub Desktop.
Save antimatter15/a04c19eb43ee94236273e2a43f8fbcc8 to your computer and use it in GitHub Desktop.
Continuations over REST
// set up a mock HTTP client/server API
// should be pretty easy to replace this with
// the real ones
var endpoints = {}
async function fetch(path, opts){
let data = await endpoints[path](JSON.parse(opts.body))
return { async json(){ return data } }
}
async function serve(path, fn){
endpoints[path] = fn;
}
// a cute little helper data structure
// call .pop() to get a promise for the next element added to queue
// call .push() to add something to a queue, fulfilling a promise if promised
function makeAwaitableQueue(){
let queue = [],
resolvers = [];
return {
pop(){
if(queue.length > 0) return Promise.resolve(queue.shift());
return new Promise((resolve, reject) => resolvers.push(resolve) )
},
push(payload){
if(resolvers.length > 0) resolvers.shift()(payload)
else queue.push(payload);
}
}
}
// clientside RPC call function supporting function parameter arguments
async function call(path, ...args){
let message = {
type: 'call',
args: args.map((k, i) =>
typeof k === 'function' ? '__func' : k)
}
while(true) {
let result = await fetch(path, {
method: 'POST',
body: JSON.stringify(message),
})
let data = await result.json()
if(data.type == 'callback'){
let value = await args[data.index](...data.args)
message = { type: 'response', id: data.id, value: value }
}else if(data.type == 'return'){
return data.value
}
}
}
// serverside RPC register function supporting function parameter arguments
function register(path, fn){
let callbacks = []
serve(path, async function(body){
if(body.type == 'call'){
let queue = makeAwaitableQueue();
Promise.resolve(fn(...body.args.map((k, i) =>
(k === '__func') ? (...args) => new Promise((resolve, reject) => {
let id = callbacks.length;
callbacks[id] = function(value){
resolve(value)
return queue.pop()
}
queue.push({ type: 'callback', id: id, index: i, args: args })
}) : k))).then(value =>
queue.push({ type: 'return', value: value }))
return queue.pop()
}else if(body.type == 'response'){
return await callbacks[body.id](body.value)
}
})
}
// register an example reduce endpoint
register('/reduce', async function(list, fn){
if(list.length === 0) return null;
if(list.length === 1) return list[0];
let init = await fn(list[0], list[1])
for(let i = 2; i < list.length; i++){
init = await fn(init, list[i])
}
return init
})
// example using the reduce endpoint
;(async function(){
let result = await call('/reduce', [1, 2, 3, 4], (a, b) => {
return a + b
})
console.log('final result: ', result)
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment