~/
Async Patterns & Errors
Quiz
...

Async Patterns & Errors

intermediate · updated Tue Sep 22 2026Contribute

Master asynchronous programming in JavaScript: learn how to use callbacks, promises, and async/await to handle operations and failures effectively.

Async Patterns & Errors

Node is built for asynchronous work. Knowing how to write and handle async code — and how to fail gracefully — is essential.

Callbacks (the original)

Early Node used error-first callbacks:

import { readFile } from "node:fs";

readFile("notes.txt", "utf8", (err, data) => {
  if (err) {
    console.error(err);
    return;
  }
  console.log(data);
  // → "Milk\nBread" once the read succeeds
});

The convention: the first argument is an error, the second the result. This works but nests badly — “callback hell”.

Promises

A Promise represents a future value. It is either pending, fulfilled, or rejected:

import { readFile } from "node:fs/promises";

readFile("notes.txt", "utf8")
  .then((data) => console.log(data))    // → the file contents
  .catch((err) => console.error(err));  // → the error message

async / await

async/await lets you write async code that reads like synchronous code:

async function load() {
  try {
    const data = await readFile("notes.txt", "utf8");
    console.log(data); // → "Milk\nBread"
  } catch (err) {
    console.error(err);
  }
}

An async function always returns a Promise. await pauses that function only — the rest of the program keeps running.

Running in Parallel

Do not await independent tasks one by one:

// slow — sequential
const a = await getUser(1);
const b = await getUser(2);

// fast — parallel
const [a, b] = await Promise.all([getUser(1), getUser(2)]);

Promise.all rejects as soon as any promise rejects. Promise.allSettled waits for all and reports each result.

Central Error Handling

Do not swallow errors. Either handle them where you can recover, or let them bubble:

async function main() {
  await startServer();
}

main().catch((err) => {
  console.error("Fatal:", err);
  process.exit(1);
});

Async Iteration

Streams and async generators work with for await...of:

for await (const chunk of stream) {
  process.stdout.write(chunk);
}

Best Practices

  1. Prefer async/await — Clearer than raw promises or callbacks.
  2. Await in parallel when independent — Promise.all cuts latency.
  3. Never leave a promise unhandled — It can crash the process.
  4. Handle errors at the boundary — Top-level .catch as a last resort.

Common Mistakes

  1. Forgetting await — You get a Promise, not the value.
  2. await inside a loop — Runs sequentially; batch instead.
  3. Swallowing errors — Empty catch blocks hide bugs.
  4. Assuming order — Async callbacks do not run in the order you wrote them.