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
setTimeoutandsetIntervalto 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 withPromise.all, abort operations by rejecting, and recover from other promises' rejections withcatch. 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,
doubleandadd3. 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
thencallbacks, 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();Async Result:
{fulfilled: 13} 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();Async Result:
{fulfilled: 13} Notice how much more "normal" this example looks. We call
doubleand pass its result directly toadd3, rather than connecting the two withthen. If it weren't for theasyncandawaitkeywords, we wouldn't even know that anything asynchronous is happening.Our new async/await
computefunction still returns a promise. But, we no longer have to usethen. 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 asasync function { ... }instead of justfunction { ... }.awaitdoesn't work withoutasync, andasyncisn't useful unless weawaitsomewhere in the function.Let's verify that
asyncfunctions really do return promises. If that's true, we should be able to callthenon 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);Async Result:
{fulfilled: 14} 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 weawaitthat 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();Async Result:
{fulfilled: 26} To think through what's happening here, imagine that all code after the
await double(5).then(add3)is inside athen. The next example shows that as actual running code, which (very roughly) mirrors what the JavaScript runtime does for anawaitinside of anasyncfunction. First, we run theawaitexpression to get a promise. Then we run the rest of the function inside athencallback.(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();Async Result:
{fulfilled: 26} That example has the same pieces as the async/await version: we call
double(5).then(add3), we assign the result tothirteen, and we multiply that result by2.With async/await, we can still control the order of our async operations, but without having to write
thens. Inside of anasyncfunction, everyawaitwaits 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
doublefunction 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)]);Async Result:
- Goal:
{fulfilled: [4, 8, 12]}- Yours:
{fulfilled: [4, 8, 12]}
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.