What’s so great about async/await?
Here’s the issue: to the programmer, there’s nothing terribly different about synchronous and asynchronous logic. There are plenty of performance and optimization issues the programmer should be thinking about when writing asynchronous code, but having radically different syntax for synchronous and asynchronous programming makes little sense.
To illustrate, here are three examples of the same logic — first using synchronous functions, then using callbacks, then using promises. Each one will retrieve details for the top story on Hacker News.
Here’s the (hypothetical) synchronous version:
This is nice and straightforward — nothing new for anyone who’s written code before. Just three steps: get a list of story IDs, get the details for the top story, and print the result.
Here’s the same logic using callbacks (again, hypothetical):
Oof. Now our steps are nested one inside the other, and we have to indent again for each step. If we had 20 steps instead of 3, the last step would have a 40-space indent! And if you want to add a new step in the middle, you need to add an indent to everything below it, creating huge, spurious diffs in Git. Also notice that we have to handle errors at every step along the way — there’s no way for us to group a set of operations in a single
Let’s try again with Promises:
OK, this looks better. Our three steps are all aligned horizontally, and adding a new step in the middle is as simple as inserting a new line. That said, it’s still a little verbose with the
Promise.resolve() and all the
.then()s hanging around. Let’s see how it looks with async/await:
Much better! This looks just like our synchronous code, except with the
await keyword thrown in. We’ve also surrounded our code with an anonymous
async function to make the example more copy-pasteable (see below).
Under the hood, both
Upgrading to async/await
So how can you start using async/await in your project? If you’re already using Promises, you’re ready to go! Any function that returns a Promise can also be called using
await, which will cause it to return the resolved value. Callbacks, however, will need to be converted to Promises before you can use
Upgrading from Promises
If you were an early adopter of ES6 Promises, and your code currently uses chains of
.then()s to execute asynchronous logic, upgrading to async/await is straightforward: just replace each
.then() with an
You should also replace
.catch() with standard
try/catch blocks — finally we can use a single syntax for error handling in both synchronous and asynchronous contexts!
Last, note that
await can’t go in the top-level of your module — it needs to be inside an
Upgrading from Callbacks
If your code still uses callbacks, the best way to move forward is to Promisify your callbacks, then use
await on any function that returns a Promise. See the “Promisifying Callbacks” section here.
Patterns and Gotchas
Of course, nothing new comes without growing pains. Here are a few patterns and pitfalls you might encounter when migrating to async/await:
Array.forEach over the traditional
If you’re using
Array.forEach won’t work, since it returns synchronously:
In this example,
forEach will start a bunch of concurrent asynchronous calls to
getItem() and return immediately, without waiting for the responses, so
done! is the first thing that’s printed.
If you want to wait until the loop is finished, you either need a regular
for loop (which will run in series), or a
Promise.all (which will run concurrently):
The best part about async/await is also the biggest pitfall — you no longer have to think about writing asynchronous code, so you’ll be tempted to forget that small tweaks can have huge effects on performance.
As an example, say we want to get two Hacker News users and compare their karma. Here’s the naive implementation:
This works fine, but the second
getUser() won’t run until the first call is finished. We should be running those calls concurrently!
Here’s a better solution:
Be sure to stay mindful of what can and can’t be done concurrently.
If you’d like to start playing with async/await, DataFire provides open source clients for services like Hacker News, GitHub, MongoDB, and Slack that are compatible with both Promises and async/await.