Whilst Promises provide an mechanism to relate the (considerable) body of callback-based code with async functions, the syntax is relatively verbose and implementation dependent (i.e. it requires and relies on knowledge of the spawn() function) potentially restricting the possible set of implementations.
This issue suggests a mechanism to minimize the boilerplate needed to interface existing code with async/await by allowing callbacks contained within async functions to resolve and reject their container async functions, and prevent the container resolving automatically on exit without a return or throw.
Consider the code below, which has only 1 functional line (2 including the declaration):
function sleep(t) {
return new Promise(function(resolve,reject) {
setTimeout(resolve,t) ;
}) ;
}
An alternative would be to pass the resolve/reject parameters to the re-written generator with two additional parameters (in this example asyncReturn and asyncThrow), parsing
async function sleep(t) {
setTimeout(asyncReturn,t) ;
}
to
function sleep(t) {
return spawn(function*(asyncReturn,asyncError){
setTimeout(asyncReturn,t) ;
}) ;
}
The same translation works for direct calls as well:
async function get(url) {
var x = new XMLHttpRequest() ;
x.onload = function(){
if (x.status===200)
return asyncReturn(x.responseText) ;
else
return asyncError(new Error(x.responseText)) ;
} ;
x.open(url) ;
x.send() ;
}
...translates to
function get(url) {
return spawn(function*(async,asyncError){
var x = new XMLHttpRequest() ;
x.onload = function(){
if (x.status===200)
return asyncReturn(x.responseText) ;
else
return asyncError(new Error(x.responseText)) ;
} ;
x.open(url) ;
x.send() ;
})
}
This can be easily implemented by:
- Passing the Promise resolve and reject routines in spawn():
function spawn(genF,self) {
return new Promise(function(resolve, reject) {
var gen = genF.call(self,resolve,reject);
....
- Allowing async functions to NOT automatically resolve on return, but providing a method to indicate resolution will take place in the future. I have implemented this by disallowing the resolved value to be the
resolver
argument (since this is pointless), and always appending a synchronous return to the wrapped generator body.
The rewrite rule now becomes:
async function <name>?<argumentlist>{<body>}
=>
function <name>?<argumentlist>{ return spawn(
function*(asyncReturn,asyncError) {<body> ; return asyncReturn ; },
this); }
...and spawn is updated to:
function spawn(genF,self) {
return new Promise(function(resolve, reject) {
var gen = genF.call(self,resolve,reject);
function step(nextF) {
var next;
try {
next = nextF();
} catch(e) {
// finished with failure, reject the promise
reject(e);
return;
}
if(next.done) {
// finished with success, resolve the promise
// ...unless the response indicates resolution by callback
if (next.value!==resolve)
resolve(next.value);
return;
}
// not finished, chain off the yielded promise and `step` again
Promise.cast(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}
Syntax:
The use of the identifier asyncReturn
(and asyncError
) is possibly not wise. Additional rewrites would be required, but preferential syntaxes might be:
return async <expr> => return <arg1>(<expr>)
throw async <expr> => return <arg2>(<expr>)