This proposal is a possible alternative to A proposal for async style sugar.
The syntax and usage of async
and await
would look very similar to that of JavaScript, e.g.
let doSomething = async (arg) => {
let value1 = await asyncOp1(arg);
...
await asyncOp2(value1);
}
This would compile to the following JavaScript:
let doSomething = async (arg) => {
let value1 = await asyncOp1(arg);
...
return await asyncOp2(value1);
}
In order to simplify the implementation, both async
and await
would be converted to “apply” nodes by the parser just like regular function calls. This allows us to leverage the existing type checker without introducing new node types to the parse tree. The parser would also be responsible for checking if await
was being used outside of an async
function.
Programs using async/await would have external declarations for these functions providing they types necessary for the program to be type checked, e.g.
@val external _await: (Js.Promise.t<'a>) => 'a = "await"
@val external _async0: (() => 'a) => (() => Js.Promise.t<'a>) = "async"
@val external _async1: (('a) => 'b) => (('a) => Js.Promise.t<'b>) = "async"
...
let doSomething = _async1((arg) => {
let value1 = _await(asyncOp1(arg));
...
_await(asyncOp2(value1));
}
The frontend would then be responsible for convert _async0
, _async1
, …, and _await
calls back to JavaScript async/await syntax.
This is similar to the approach I used to implement tagged template support in https://github.com/kevinbarabash/rescript-compiler/pull/2, albeit a bit more complicated because of the introduction of new keywords.
One limitation of this approach is that it requires async
functions to await
any promise before returning its value so that the types work. As an optimization, we could convert return await foo;
to return foo;
since the generated output since JS does automatic promise coalescing with async functions.
Here’s a playground link that shows the intermediate type-checking in action.