How is async/await transpiled to ES5
In short, ES7’s async/await
syntax is used to turn asynchronous code flows with Promises
into synchronous ones.
The proposal and examples can be seen here.
We will look at the implementation details of ES7’s async/await
to get a better understanding of it.
You can use babel-preset-stage-3
with its transform-async-generator-functions plugin to transpile the async/await
syntax ES6’s generator
functions.
Then the generators
are again transformed to ES5 functions with state, the process is described in this blog post.
All there is left to understand is how to reduce async
functions to generators
.
async/await to generators
Suppose we have an async
function - let’s take the example from the proposal:
async function chainAnimationsAsync(elem, animations) {
let ret = null;
try {
for(const anim of animations) {
ret = await anim(elem);
}
} catch(e) { /* ignore and keep going */ }
return ret;
}
Rewriting it with generators requires the following high-level ideas:
- Turn the
async
function into a generator function and make it return aPromise
- Replace each
await
in the function’s code with ayield
We accomplish the first goal by creating an iterator for the generator function
and iterating it until the function is finished or an error occurred.
We wrap this behavior into a function called spawn
.
function chainAnimationsGenerator(elem, animations) {
// spawn will return a Promise
return spawn(function*() {
let ret = null;
try {
for(const anim of animations) {
ret = yield anim(elem);
}
} catch(e) { /* ignore and keep going */ }
return ret;
});
}
In essence, the spawn
function wraps the result (coming from the generator’s yield) into a promise and keeps iterating the generator in the promise’s resolve callback (while passing the resolved value as the new argument to yield). This chaining of the code execution through promises is what makes it possible to execute asynchronous code as it was synchronous code:
function spawn(genFunc, self) {
return new Promise(function(resolve, reject) {
// start iterating the original function and set correct this pointer
var iterator = genFunc.call(self);
function step(nextFunc) {
var next;
try {
next = nextFunc();
} catch(e) {
// finished with failure, reject the promise
reject(e);
return;
}
if(next.done) {
// finished with success, resolve the promise
resolve(next.value);
return;
}
// not finished, chain off the yielded promise and `step` again
Promise.resolve(next.value).then(function(v) {
// keep stepping until next yield (original await) passing new value to yield
step(function() { return iterator.next(v); });
}, function(e) {
step(function() { return iterator.throw(e); });
});
}
// keep stepping until next yield (original await)
step(function() { return iterator.next(); });
});
}
Update: I highly recommend reading Ben Nadel’s article for a more in depth explanation.