Skip to content

Async / Await

JavaScript is infamous for its tricky asynchronous code. Fortunately, it's gotten way nicer to work with over the past decade!

The newest tool in the toolbox is the pair of async and await keywords. Using these keywords, we can write asynchronous code that looks and feels like the synchronous code we know and love.

In this lesson, we'll learn how async/await works.

Basic idea

Let's suppose we want to write a function that copies a URL to the user's clipboard, so they can easily paste it and go.

The following code looks good, but it doesn't actually work properly:

function copyUrlToClipboard(url) {
try {
navigator.clipboard.writeText(url)
.then(() => {
console.info('Successfully copied URL');
});
} catch (err) {
return { error: err }
}
return { success: true }
}

Here's the problem: That writeText produces a promise.

Promises are asynchronous, meaning that they don't "block" the code. Our program doesn't wait to see if the operation completes successfully or not, it immediately goes onto the next line.

This means that our program will always return { success: true }, even if the method fails!

In general, we can't use try/catch with asynchronous code. By the time the exception is thrown, we've already resolved the try/catch statement!

Here's the order that the code runs in:

Illustrated diagram of the code snippet above showing the order of operations

Hard to follow, right?

Here's the same code, using async/await:

async function copyUrlToClipboard(url) {
try {
await navigator.clipboard.writeText(url);
console.info('Successfully copied URL');
} catch (err) {
return { error: err }
}
return { success: true }
}

By switching to async/await, we fix both problems:

  1. The try/catch now works as intended. If the writeText method throws an exception, we'll return that error.
  2. The code runs in the order we'd expect, from the first line to the last.

How it works

The first step when using async/await is that we need to declare that the function is an “async function”, by prefixing the keyword async before the function.

This works with both standard functions and arrow functions:

// Standard function:
async function doSomethingAsynchronous() { }
// Arrow function:
const doSomethingAsynchronous = async () => { }

Within an async function, the await keyword can be used to "pause" the function until the async operation has completed:

async function doMultipleThings() {
await doFirstThing();
await doSecondThing();
return "done!"
}

In this example, we wait until doFirstThing has resolved before moving on to doSecondThing. Then, we wait until doSecondThing is done before returning.

(Technically speaking, this code is still asynchronous. We're not blocking anything, and it's just as performant as promises!)

The await expression also produces a value, and we can assign that value to a variable:

// This function adds two numbers together,
// but instead of doing the work synchronously,
// it returns a promise.
//
// (This is a silly thing to do! But more-realistic
// examples are annoyingly complicated.)
function addNumsAsync(num1, num2) {
return new Promise((resolve) => {
resolve(num1 + num2);
})
}
async function doTheMath() {
const result = await addNumsAsync(1, 2);
console.log(result); // 3
}

When we await a promise, it waits to see how that promise will resolve, and forwards the resolved value along, letting us assign it to a variable!

Ultimately, async/await is . We can do all of the same stuff using promises. But, in my opinion, async/await is significantly friendlier and easier to follow. I've been using async/await as my preferred tool for async code for a few years now, and it's been incredibly helpful and worthwhile.