After this lesson you can
- Await a promise and handle its rejection
- Say why a floating promise escapes try/catch
- Run independent work in parallel rather than in sequence
A promise is a value that is not ready yet. await waits for it.
async function load(id: string): Promise<User> {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json() as Promise<User>;
}
An async function always returns a promise, whatever you write inside it.
Returning 5 returns Promise<number>.
The try/catch that never catches
try {
sendWelcomeEmail(user.email); // no await
} catch (err) {
logger.warn(err); // never runs
}
try guards the synchronous execution of its block. The call returns a
promise immediately, the block finishes, and the rejection arrives later
with nothing on the stack to catch it — as an unhandled rejection, which
under Node's default policy takes the process down.
The fix is await. Every promise should be awaited, returned, or explicitly
discarded with a comment saying why it is fire-and-forget. A bare promise
used as a statement always deserves a second look.
Sequential against parallel
const a = await loadUser(id); // waits
const b = await loadOrders(id); // then waits again
Those two do not depend on each other, so waiting twice wastes the first
wait. Promise.all starts both and waits once:
const [a, b] = await Promise.all([loadUser(id), loadOrders(id)]);
Fifty sequential calls at 500 microseconds each is 25 milliseconds of pure waiting. In parallel it is closer to one round trip.
Promise.all rejects as soon as any one rejects. When you want every
result including the failures, Promise.allSettled gives you both.
Try it
// Each call logs when it starts and when it finishes, so the output// shows how the waits overlap without depending on the clock.function slow(label: string, log: string[]): Promise<void> { log.push(`start ${label}`); return new Promise((resolve) => setTimeout(() => { log.push(`end ${label}`); resolve(); }, 20), );} export async function run(): Promise<Record<string, string>> { const sequential: string[] = []; await slow("a", sequential); await slow("b", sequential); await slow("c", sequential); const parallel: string[] = []; await Promise.all([slow("a", parallel), slow("b", parallel), slow("c", parallel)]); return { sequential: sequential.join(", "), parallel: parallel.join(", ") };}forEach does not wait
forEach calls your callback and throws away whatever it returns — including
the promise an async callback returns. Nothing waits for it:
function wait(ms: number): Promise<number> { return new Promise((resolve) => setTimeout(() => resolve(ms), ms));} export async function run(): Promise<number[]> { let viaForEach = 0; [10, 20, 30].forEach(async (ms) => { viaForEach += await wait(ms); // forEach ignores the promise this returns }); const rightAfterForEach = viaForEach; let viaForOf = 0; for (const ms of [10, 20, 30]) { viaForOf += await wait(ms); // the loop itself waits } // By now the forEach callbacks have finished too; nobody waited for them. return [rightAfterForEach, viaForOf, viaForEach];}Use for...of with await when the steps must happen in order, or
await Promise.all(items.map(async ...)) when they may overlap.
Common mistakes
forEachwith anasynccallback. Shown above: the total is0when the code moves on, and a shared+=across awaits loses updates as well.- Calling without
awaitinsidetry. The rejection arrives after thetryhas finished, so thecatchnever runs. - Awaiting independent calls one after another. Correct, but each wait
is added to the last;
Promise.alloverlaps them. - Expecting
Promise.allto cancel the rest. When one rejects, the others keep running; their results are simply ignored. Promises have no built-in cancellation.
What interviewers ask
- What happens when an
asyncfunction throws? It does not throw at the call site; it returns a rejected promise, seen only byawaitor .
Exercise · Pro
Add up every delay, without waiting for them one at a time
Exercises are free in the first 3 lessons of every course. From here on they are part of Pro and Sprint; the lesson and its runnable examples stay open.
Upgrade