Execute Program

JavaScript Concurrency: Async Await

Welcome to the 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!

  • We started this course with callbacks, using setTimeout and setInterval to call functions later. Then we saw promises, which are built on callbacks but give us much more control over async operations. For example, we can wait for multiple promises with Promise.all, abort operations by rejecting, and recover from other promises' rejections with catch. Switching from callbacks to promises was mostly about adding new capabilities.

  • Now we're going to introduce the third major concurrency system in JavaScript: async/await. Async/await is built-in language syntax to make promise code easier to read and write. Unlike the switch to promises, we won't be adding new capabilities. Instead, we'll be simplifying the functionality we already learned.

  • Here's a code example to get us started. We'll define two functions, double and add3. They both do what their names suggest, but return promises that take 500 ms to fulfill with their result. We can think of these functions as stand-ins for API calls, database reads, or any other operation that will take some time to finish.

  • >
    function double(n) {
    return new Promise(resolve => {
    setTimeout(() => resolve(n * 2), 500);
    });
    }

    function add3(n) {
    return new Promise(resolve => {
    setTimeout(() => resolve(n + 3), 500);
    });
    }
  • We can combine our functions using normal then callbacks, just as we've done before.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    function compute() {
    return double(5).then(add3);
    }

    compute();
    Asynchronous Icon Async Result:
    {fulfilled: 13}Pass Icon
  • Here's the same exact code using async/await. It also returns a promise.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    async function compute() {
    return await add3(await double(5));
    }

    compute();
    Asynchronous Icon Async Result:
    {fulfilled: 13}Pass Icon
  • Notice how much more "normal" this example looks. We call double and pass its result directly to add3, rather than connecting the two with then. If it weren't for the async and await keywords, we wouldn't even know that anything asynchronous is happening.

  • Our new async/await compute function still returns a promise. But, we no longer have to use then. This might seem like a small difference, but it can have a big effect on readability.

  • With synchronous functions, we'd write add3(double(5)). With async/await, we make one small change: add3(await double(5)). But with promises, we have to use the functions in a "backwards" order, and also pass a function as an argument to another function: double(5).then(add3).

  • To use await, we have to define our function as async function { ... } instead of just function { ... }. await doesn't work without async, and async isn't useful unless we await somewhere in the function.

  • Let's verify that async functions really do return promises. If that's true, we should be able to call then on them.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    async function compute() {
    return await add3(await double(5));
    }

    compute().then(n => n + 1);
    Asynchronous Icon Async Result:
    {fulfilled: 14}Pass Icon
  • We can also use promises directly inside an async function. Calling double(5).then(add3) inside an async function gives us a promise, as usual. When we await that promise, our code is suspended until the promise resolves. After it resolves, we get its resolved value: 13. We can then use that value like we would in normal JavaScript code.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    async function compute() {
    const thirteen = await double(5).then(add3);
    return thirteen * 2;
    }

    compute();
    Asynchronous Icon Async Result:
    {fulfilled: 26}Pass Icon
  • To think through what's happening here, imagine that all code after the await double(5).then(add3) is inside a then. The next example shows that as actual running code, which (very roughly) mirrors what the JavaScript runtime does for an await inside of an async function. First, we run the await expression to get a promise. Then we run the rest of the function inside a then callback.

  • (We have to introduce a temporary variable to make this work. We'll name it _awaited, with the _ indicating that it's not part of the original code.)

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    function compute() {
    const _awaited = double(5).then(add3);
    return _awaited.then(fulfilledValue => {
    const thirteen = fulfilledValue;
    return thirteen * 2;
    });
    }

    compute();
    Asynchronous Icon Async Result:
    {fulfilled: 26}Pass Icon
  • That example has the same pieces as the async/await version: we call double(5).then(add3), we assign the result to thirteen, and we multiply that result by 2.

  • With async/await, we can still control the order of our async operations, but without having to write thens. Inside of an async function, every await waits for the promise before continuing.

  • Now that we understand promises, we can fully appreciate this. We get all the benefits of promises, but with clean, synchronous-looking code!

  • Here's a code problem:

    Use async/await to complete this "quadruple" ("multiply the number by 4") function. It should use the double function twice.

    function double(n) {
    return new Promise(resolve => {
    setTimeout(() => resolve(n * 2), 500);
    });
    }

    async function quadruple(n) {
    return await double(await double(n));
    }
    Promise.all([quadruple(1), quadruple(2), quadruple(3)]);
    Asynchronous Icon Async Result:
    Goal:
    {fulfilled: [4, 8, 12]}
    Yours:
    {fulfilled: [4, 8, 12]}Pass Icon
  • One final note for this lesson. If async/await looks almost like regular synchronous code, why didn't we just start with that? Why bother with callbacks and promises at all?

  • One simple answer is that real-world code is a mix of callbacks, promises, and async/await.

  • Another is that async/await is "syntactic sugar" for promises: it's just another way of writing the same thing. Understanding promises is essential to understanding how async/await works, because async/await is just another way to work with the same old promises.