async and await: Code That Reads Like a Story

Freshman Year, page 5 of 10. A two-minute read.

Promises were a huge improvement. But in 2017, JavaScript added something even nicer: async and await. They let you write asynchronous code that looks just like normal, top-to-bottom synchronous code.

Here's the same user-orders-items example, rewritten with async/await (using the same three functions):

JavaScript
async function showFirstOrder() {
  const user = await getUser(1);
  console.log(`1. Found user ${user.name}`);

  const orders = await getOrders(user);
  console.log(`2. Found ${orders.length} orders`);

  const items = await getItems(orders[0]);
  console.log(`3. First order contains: ${items.join(", ")}`);
}

showFirstOrder();

Output

1. Found user Olivia
2. Found 2 orders
3. First order contains: waffle, salsa

Isn't that beautiful? No .then(), no callbacks. It reads like a recipe.

Here are the rules:

  1. Put the keyword async in front of a function to make it an async function.
  2. Inside an async function, you can put await in front of any promise. await pauses the function until the promise settles, and then gives you its value.
  3. An async function always returns a promise.
Tipawait only pauses its own function

await pauses the async function it's in, not your whole program. While that function is waiting, JavaScript happily carries on running other code, handling clicks and so on. It's like the waiter putting one order on hold, not the whole restaurant closing.

Handling errors with try...catch

With async/await, you handle errors using the good old try...catch from Summer School:

JavaScript
function orderPizza(flavor) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (flavor === "pineapple") reject(new Error("Out of pineapple. Phew."));
      else resolve(`${flavor} pizza`);
    }, 50);
  });
}

async function dinner(flavor) {
  try {
    console.log(`Ordering ${flavor}...`);
    const pizza = await orderPizza(flavor);
    console.log(`😋 Eating a ${pizza}`);
  } catch (error) {
    console.log(`😢 No dinner: ${error.message}`);
  } finally {
    console.log("Washing up.");
  }
}

async function main() {
  await dinner("margherita");
  await dinner("pineapple");
}

main();

It prints

Ordering margherita...
😋 Eating a margherita pizza
Washing up.
Ordering pineapple...
😢 No dinner: Out of pineapple. Phew.
Washing up.

If the awaited promise is rejected, await throws the error, and the catch block catches it, exactly like a normal error.

Watch outForgetting await

If you forget await, you get the promise itself (the buzzer), not the value (the food):

JavaScript
async function getNumber() {
  return 42;
}

async function main() {
  const wrong = getNumber();
  const right = await getNumber();
  console.log(wrong);
  console.log(right);
}

main();

It prints

Promise { <pending> }
42

If you ever see Promise { <pending> } or [object Promise] where you expected real data, you probably forgot an await!

Reading is step one

CurlyScript turns these notes into a game: short questions, an explanation after every answer, and a test at the end of each grade. Free, with nothing to sign up for.

Start learning