Execute Program

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 then callbacks. 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 loggedInUserId and getUserName functions 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 sleep function 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 loggedInUserName function 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 that loggedInUserId is hard-coded, we handle the case where it might be undefined, 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();
    Asynchronous Icon Async Result:
    {fulfilled: 'Amir'}Pass Icon
  • This promise-based loggedInUserName function 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();
    Asynchronous Icon Async Result:
    {fulfilled: 'Amir'}Pass Icon
  • 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 waitForAll function, which was a simplified version of Promise.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)]);
    Asynchronous Icon Async Result:
  • The waitForAll function 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 of waitForAll written 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)]);
    Asynchronous Icon Async Result:
    {fulfilled: ['Amir', 'Betty', 'Cindy']}Pass Icon
  • That's a huge difference! We can store our results in a regular array, instead of having to continually rebuild an arrayPromise after processing every promise. And the array.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 original waitForAll, and is much easier to read.

  • You're unlikely to implement waitForAll in a real system; you should use Promise.all instead. 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.