async and await

JavaScript · Practical · 15 min

async/await is how almost all modern asynchronous JavaScript is written.

An async function always returns a promise. Inside it, `await` pauses the function until a promise settles, then gives you the fulfilled value — or throws the rejection reason:

async function showOrders(id) {
  try {
    const user = await loadUser(id);
    const orders = await loadOrders(user.id);
    render(orders);
  } catch (err) {
    showError(err);
  }
}

It's still promises underneath: the return value becomes the promise's value and a throw rejects it. Pausing doesn't block the thread; other code runs while the function waits. await also works at the top level of ES modules.

The most important performance habit: don't serialise independent work. Each await waits before the next line starts, so

const a = await getA();
const b = await getB();

takes the sum of both durations. If they don't depend on each other, start both and await together: const [a, b] = await Promise.all([getA(), getB()]).

Loops need care:

  • for...of with await inside processes items one after another — correct when order or rate limits matter.
  • array.forEach(async ...) does not wait; forEach ignores the returned promises, so the code after it runs immediately and errors go unhandled.
  • For concurrent processing, await Promise.all(items.map(async item => ...)).

Calling an async function without await (or .catch) "fires and forgets" it; any error becomes an unhandled rejection. Linters can flag floating promises. Inside try, write return await p if you want that catch to handle p's rejection.

Example

const sleep = (ms, v) => new Promise(r => setTimeout(() => r(v), ms));

async function getUser(id) { await sleep(50); return { id, name: "Ada" }; }
async function getPosts(id) { await sleep(50); return ["hello", "world"]; }
async function getFails() { await sleep(10); throw new Error("not found"); }

async function main() {
  let t = Date.now();
  const user = await getUser(1);
  const posts = await getPosts(1);                 // waits for getUser first
  console.log("sequential ~100ms:", Date.now() - t >= 100);

  t = Date.now();
  const [u2, p2] = await Promise.all([getUser(1), getPosts(1)]);
  console.log("parallel ~50ms:", Date.now() - t < 100); // true

  try {
    await getFails();
  } catch (err) {
    console.log("caught:", err.message);           // caught: not found
  }
  return `${user.name} has ${posts.length} posts`;
}

main().then(console.log);                          // Ada has 2 posts

Remember

  • An async function returns a promise; await unwraps one or throws
  • Start independent work together and await Promise.all([...])
  • forEach doesn't await — use for...of (sequential) or Promise.all + map

Watch out for

  • items.forEach(async item => await save(item)) and assuming everything is saved afterwards.

Check yourself

  1. getA() and getB() each take 1 s and are independent. How long does const a = await getA(); const b = await getB(); take?
  2. What does an async function return when its body executes return 42?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function