JavaScript Concurrency: Control Flow With Async/await
Welcome to the Control Flow With Async/await lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
In this lesson, we'll take a look at how async/await works with control flow, specifically within conditionals and loops.
We've already seen conditionals inside
thencallbacks. But this kind of code gets awkward, especially when we start nesting conditionals.Here's an example where we check for a logged-in user's ID, then retrieve that user from the database. The
loggedInUserIdandgetUserNamefunctions return hard-coded values, but in a real system they'd be checking an HTTP cookie and reading from a database.(We'll be using the shorthand
sleepfunction we wrote in an earlier lesson to simulate network delay.)>
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));async function loggedInUserId() {await sleep(500);/* In a real system, we'd get the logged-in user's ID using HTTP cookies* combined with a query to our database. For simplicity, we'll just* return 1 here. */return 1;}async function getUserName(id) {const users = {1: 'Amir', 2: 'Betty', 3: 'Cindy'}; // Our "database"await sleep(500);return users[id];}Now here's a
loggedInUserNamefunction that uses both functions above. It needs to get the current user ID (loggedInUserId) and retrieve that user's name from the database (getUserName). Ignoring the fact thatloggedInUserIdis hard-coded, we handle the case where it might beundefined, which would represent an anonymous user (who's not logged in).- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
function loggedInUserName() {return loggedInUserId().then(userId => {if (userId === undefined) {return undefined;} else {return getUserName(userId);}});}loggedInUserName();Async Result:
{fulfilled: 'Amir'} This promise-based
loggedInUserNamefunction is wordier than it has to be.Here's a version rewritten to use async/await. It removes some of the visual noise so the important part of what the function does (the conditional) is more prominent.
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
async function loggedInUserName() {const userId = await loggedInUserId();// No "then" needed! The `await` above lets us continue with a normal "if".if (userId === undefined) {return undefined;} else {return await getUserName(userId);}}loggedInUserName();Async Result:
{fulfilled: 'Amir'} This code is only slightly shorter in terms of lines. But it's noticeably more direct because it doesn't need a callback.
Now for an example that's very difficult with raw promises: loops. In an earlier lesson we saw a
waitForAllfunction, which was a simplified version ofPromise.all. Here it is again:>
function getUserName(id) {const users = {1: 'Amir', 2: 'Betty', 3: 'Cindy'};return Promise.resolve(users[id]);}function waitForAll(promises) {// Start with an empty array.let arrayPromise = Promise.resolve([]);for (const promise of promises) {// Build a new promise holding an array that contains all of the old// array elements, plus the one from this promise.arrayPromise = arrayPromise.then(array =>promise.then(value =>[...array, value]));}return arrayPromise;}/* Once the `waitForAll` function is written, waiting for multiple API* calls is easy! */waitForAll([getUserName(1), getUserName(2), getUserName(3)]);Async Result:
The
waitForAllfunction is tricky. In fact, the lesson where we introduced it was one of the few places where we said "don't worry about understanding every detail here." But with async/await, we can rewrite this function in a simple and straightforward way! Here's a version ofwaitForAllwritten with async/await.>
function getUserName(id) {const users = {1: 'Amir', 2: 'Betty', 3: 'Cindy'};return Promise.resolve(users[id]);}/* This function is difficult to implement with promises, but it's easy* with async/await! It's similar to Promise.all. */async function waitForAll(promises) {const array = [];for (const promise of promises) {array.push(await promise);}return array;}waitForAll([getUserName(1), getUserName(2), getUserName(3)]);Async Result:
{fulfilled: ['Amir', 'Betty', 'Cindy']}That's a huge difference! We can store our results in a regular array, instead of having to continually rebuild an
arrayPromiseafter processing every promise. And thearray.push(await promise)line is simpler than the promise equivalent, which required three lines with two nested callbacks! Our new function accomplishes the exact same thing as the originalwaitForAll, and is much easier to read.You're unlikely to implement
waitForAllin a real system; you should usePromise.allinstead. But situations like this come up frequently in complex promise code, especially when combining loops with promises. Using async/await can radically simplify some promise code at almost no cost.