14 / 20 · Day 5
Day 5 · Concept 14

async / await

async marks a function as promise-returning. await unwraps a promise inline. The code reads like sequential, blocking I/O — but it isn't blocking. The runtime is yielding to other tasks at every await.


1 · The basics

js basic.js
function delay(ms, v) { return new Promise(r => setTimeout(() => r(v), ms)); }

async function run() {
    const a = await delay(20, "first");
    const b = await delay(20, "second");
    return `${a} then ${b}`;
}

run().then(console.log);   // "first then second"

An async function always returns a promise. The await keyword unwraps the promise when it settles.

2 · Try/catch for errors

js error.js
async function risky() {
    throw new Error("kaboom");
}

async function main() {
    try {
        await risky();
    } catch (err) {
        console.log("caught:", err.message);
    } finally {
        console.log("done");
    }
}

main();

Replaces .then().catch(). Same semantics, sequential-looking code.

3 · Parallel — don't await in a loop

js parallel.js
function delay(ms, v) { return new Promise(r => setTimeout(() => r(v), ms)); }

// Slow — sequential (3 * 50 = 150ms)
async function slow() {
    const a = await delay(50, 1);
    const b = await delay(50, 2);
    const c = await delay(50, 3);
    return [a, b, c];
}

// Fast — parallel (~50ms total)
async function fast() {
    return Promise.all([delay(50, 1), delay(50, 2), delay(50, 3)]);
}

(async () => {
    console.time("slow"); await slow(); console.timeEnd("slow");
    console.time("fast"); await fast(); console.timeEnd("fast");
})();

4 · Top-level await

js top-level.mjs
// In ES modules (.mjs), await at the top level
const data = await fetch("/api/health").then(r => r.json());
console.log(data);

// In CommonJS, wrap in an IIFE
(async () => {
    const data = await someAsync();
    console.log(data);
})();

5 · Common mistakes

  • Awaiting in forEach. arr.forEach(async x => await ...) doesn't wait. Use for-of with await, or Promise.all(arr.map(async ...)).
  • Sequential when parallel works. If calls don't depend on each other, use Promise.all.
  • Forgetting that async functions return promises. Calling run() without await or .then fires it but ignores the result and any error.
  • Mixing async and non-async in chains. x.then(asyncFn).then(...) — the next .then receives the unwrapped value, but be deliberate.

6 · When it clicks

  • You reach for async/await over .then for most code.
  • Promise.all in an async function is reflex when calls are independent.
  • You catch unawaited promises in code review.
Found this useful?