Skip to main content

async/await: Sequential Shape, Concurrent Power

intermediate15 min readLesson 73 of 143

Async functions, await semantics, try/catch over async code, and loops — sequential await vs parallel Promise.all.

async/await is promise syntax sugar — same machinery, readable shape.

The rules

  • async function always returns a promise; returning a value wraps it, throwing rejects it.
  • await pauses only the async function, not the thread: the rest of the program keeps running (event loop lesson).
  • await works on any thenable — and unwraps in a microtask, so await order follows microtask order.
async function loadDashboard(userId) {
  try {
    const user = await getUser(userId); // sequential: needs user.id
    const orders = await getOrders(user.id);
    return { user, orders };
  } catch (err) {
    return { error: "Could not load dashboard" }; // catches both awaits
  }
}

try/catch works across await — one of its best features. Remember it only catches errors inside the try block at await time; a forgotten await leaks a floating promise whose rejection nothing catches.

The classic trap: await in a loop

// SLOW: one request at a time (unless each needs the previous)
for (const id of ids) results.push(await fetchOne(id));

// FAST: all in flight at once
const results = await Promise.all(ids.map(fetchOne));

Sequential await is correct when step N needs step N-1's output; otherwise it is wasted latency. The combinator lesson formalizes this.