Bitsy Explains
Home/JavaScript, Explained Like You're Five/Lesson 19

async/await in JavaScript

async and await let you use promises in code that reads top to bottom like a story. Learn the syntax, try/catch for errors, and Promise.all for speed.

Lesson 19 of 203 min readVideo
Video thumbnail: async/await Watch the 90-second video
Explain like I'm five

await means wait here until the buzzer goes off, then keep going. Your code reads like a normal story, top to bottom, with no .then needed. Underneath, it is still the same promises.

async on the function, await on the promise

Put async before the function. Then put await before any promise inside it. The function pauses at that line until the promise is fulfilled, and await hands you the value.

function fetchPizza() {
  return new Promise(resolve => setTimeout(() => resolve("pizza"), 300));
}
async function dinner() {
  const pizza = await fetchPizza();
  console.log("Eating " + pizza); // prints: Eating pizza
}
dinner();
console.log("Doing homework"); // prints: Doing homework

"Doing homework" prints first. Only dinner waits. The rest of your program keeps running, and dinner picks up where it left off once the pizza arrives.

An async function always returns a promise, so other code can await it or use .then on it.

Compare it with the same job written using .then.

const fetchPizza = () => Promise.resolve("pizza");
fetchPizza().then(pizza => console.log("Eating " + pizza)); // prints: Eating pizza

For one step, both are short. With three or four steps, the async version stays a plain list of lines, and each result gets a normal variable name you can use further down. That is the main reason people prefer it. Nothing new happens underneath. await is a nicer way to write the same .then.

When it fails: try and catch

If the promise is rejected, await throws the error. Put the risky part in try, and JavaScript jumps straight into catch when something goes wrong.

function fetchPizza() {
  return Promise.reject(new Error("kitchen fire"));
}
async function dinner() {
  try {
    const pizza = await fetchPizza();
    return "Eating " + pizza;
  } catch (err) {
    return "No dinner: " + err.message;
  }
}
dinner().then(msg => console.log(msg)); // prints: No dinner: kitchen fire

This is the same try and catch you use for normal errors, so you don't need a separate style for slow code.

The twist: Promise.all for speed

Starting one thing, waiting for it, and only then starting the next is slow. If two jobs don't depend on each other, start both right away and let Promise.all wait for them together.

const wait = (ms, food) => new Promise(r => setTimeout(() => r(food), ms));
async function order() {
  const [a, b] = await Promise.all([wait(200, "pizza"), wait(200, "juice")]);
  return [a, b];
}
order().then(meal => console.log(meal)); // prints: [ 'pizza', 'juice' ]

Both jobs run at the same time, so this takes about 200 milliseconds instead of 400. The results come back in the same order you listed the promises. If any one of them is rejected, Promise.all is rejected too.

One more thing. await only works inside an async function, or at the very top level of a module, which is a special kind of JavaScript file.

When to use it

Use async and await for most new code that deals with promises, especially when there are several steps in a row. It reads like a recipe, and errors use the familiar try and catch.

.then is still fine for a quick one-step job. Both styles work with the same promises, so you can mix them, like calling .then on the result of an async function.

A good habit is to look at each await and ask whether it really needs the line above it to finish first. If it doesn't, start the jobs together and use Promise.all. Waiting in a row when you don't have to is the most common way async code ends up slower than it should be.

Rule of thumb

Put async on the function and await on the promise.

Common mistake

Forgetting await. Without it, const pizza = fetchPizza(); gives you the promise itself, the buzzer instead of the food, which logs as something like Promise { <pending> }.

Check yourself

Inside an async function, what does const pizza = await fetchPizza(); do?