Stop 04 · JavaScript & TypeScript for BackendTopics · 2/12

Async Patterns

Part of the Backend Developer Roadmap

In short

A backend spends most of its time waiting: for the database, for another API, for a file. Async code lets one Node process serve many users while it waits.

The practical patterns for structuring async backend code cleanly — sequential vs parallel awaits, error propagation, and avoiding callback hell in real route handlers.

The mascot sits on a bank bench with a token slip, reading a comic book.

Think of it like this

The bank token counter

At the bank you take a token and sit down. You don't stand frozen at the counter; you read a comic. When your number is called, you go up. A Promise is that token: a promise of a result later, while you do other work.

Watch it happen

What happens around an await

enqueue(request A: GET /orders)
request A: GET /orders

Front of the queue is coloured

1/5Request A arrives. The handler calls await db.query(...) and pauses.

The big ideas

What you need to know

A Promise is a future value

It's pending, then either fulfilled with a value or rejected with an error. await waits for it without blocking Node.

await only pauses that function

Other requests keep running. That's why one Node server can handle many users at once.

Run independent work together

await Promise.all([a(), b()]) starts both at once. Two awaits in a row run one after the other.

allSettled for partial results

Promise.allSettled waits for all and tells you which failed, instead of failing everything at the first error.

See it in code

Walk through the code

js
1// Slow: about 300ms + 200ms, one after another2const user = await getUser(id);     // 300ms3const orders = await getOrders(id); // 200ms4 5// Fast: about 300ms, both start together6const [u, o] = await Promise.all([getUser(id), getOrders(id)]);7 8// Keep going even if one source fails9const results = await Promise.allSettled([getWeather(), getNews()]);10for (const r of results) {11  if (r.status === "fulfilled") console.log(r.value);12  else console.error("failed:", r.reason.message);13}

1/3Each await waits for the one before it, so total time adds up.

Use sequential awaits only when the second call needs the first one's result. Otherwise, run them together.

Save yourself the pain

Common mistakes

Oops

Calling an async function without await, then using the result as if it were data.

Do this instead

An async function returns a Promise. await it, or the value is a pending Promise, not your data.

Oops

Using await inside a for loop for 100 independent API calls.

Do this instead

Map them to Promises and use Promise.all, maybe in small batches to avoid flooding the other server.

Your mission

Time the difference

~20 min

Quick check

Promise.all([a(), b(), c()]) where b() rejects. What happens?

Go deeper

  1. 1Refactor a route handler with multiple sequential awaits into parallel ones where possible.
  2. 2Write a reusable `asyncHandler` wrapper to catch errors in Express route handlers.
  3. 3Practice chaining dependent async calls (e.g. create user, then send welcome email) correctly.

More topics at this stop

Stuck on this topic? Ask an Insider

Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.

Download
InsideEdge