The time has come to experiment with a much sought after feature for ReScript - dedicated async/await support!
For the past several weeks, @cristiano has been experimenting with what it’d take to implement async/await in ReScript, and it’s a great pleasure to tell you that we have a working prototype.
The goal with this experimentation has been to try and stay as close to the JS equivalent as possible. This effectively means that async/await will, in its final form, look just like the JS equivalent:
let greetUser = async (userId) => {
let name = await getUserName(. userId)
"Hello " ++ name ++ "!"
}
Note: We’re using an uncurried function call here, to show how clean the output can be.
…and the generated JS output for this will emit native async/await syntax:
async function greetUser(userId) {
var name = await getUserName(userId);
return "Hello " + name + "!";
}
Release?
We’d like to invite the community to try the protoype and give us feedback. Please join in and tell us your thoughts!
You can test the current prototype by following the instructions found here.
As for a release, provided everything works out, we think async/await could go into the next big release after v10. We will label it an experimental feature in the first release, just to set the expectations appropriately.
Read along for more details on the experiment and everything around it.
The experiment
First, a note: there’s no 1st class syntax support yet in the experiment, but an actual release of async/await will see full 1st class syntax support. It’ll look like the ReScript code snippet at the top of this post. However, in the experiment, it’s implemented as attributes (@async
, @await
) for convenience of experimenting and testing.
Here’s a commit in the ReScript documentation repository that shows how a function returning a promise is refactored to async/await, including what the JS output ends up being.
Here’s another link to an extensive set of examples that has been tried during the experimentation (and here’s the JS output of that).
More details
Error handling is important, especially in async/await. JS requires wrapping await’s in try/catch to handle errors. ReScript syntax opens up for slightly different error handling than in JS.
Ergonomic syntax for handling errors
Error handling is done by adding a switch case for exception JsError
:
let someAsyncFn = async () => {
let maybeSomeValue = switch await somethingThatMightThrow() {
| data => Some(data)
| exception JsError(_) => None
}
}
This will automatically wrap await somethingThatMightThrow()
in a JS try/catch, meaning errors stemming from the await will be caught.
There’s an additional thing we’re exploring that’ll help with error handling too.
Built in static analysis for error handling
We’ve experimented with extending Reanalyze to statically track error handling in async functions. What this means in practice is that Reanalyze can tell you whether you’re handling all potential errors as you’re awaiting values which could throw.
This is another step we’re taking to nudge developers to not forget handling errors in async/await.
It also opens up for adding code actions to our editor tooling that can automatically insert the skeleton for error handling where Reanalyze tells you that you’ve missed handling a potential error.
More details on this will come at a later point.
Compiler warnings for nested promises
This is an improvement in general for promises in the ReScript type system. For those who don’t know, promises in the ReScript type system can be nested (promise-in-promise). However, in runtime the JS engine flattens all nested promises into a single one, meaning any level of promise-in-promise will ultimately produce a single promise resolving to a single value in runtime. This is a problem in ReScript, because the type system can’t flatten promises like that. That leads to potential runtime issues when you have nested promises, unless you add specific runtime code to handle it. Read more on this issue here.
In tandem with the async/await work, @cristiano has built a compiler warning that will catch a lot of these nested promises. This will help us avoid causing runtime issues by warning whenever there’s a nested promise somewhere in your program.
Wrapping up
We’re excited for this, and we hope bringing async/await to ReScript is going to help smoothen the experience for developers adopting ReScript who are used to having async/await in their tool belt from JS/TS.
There’s some work left to finish the v10 release. Still, we look forward to hearing your thoughts, as well as finalizing this feature in time for the next big release after v10.
Looking forward to hearing what you think!