NelsonLabs
Node.js Fundamentals/Async Patterns in Node

Async Patterns in Node

Node.js was built around asynchronous, non-blocking I/O. Understanding why this matters β€” and how async patterns evolved from callbacks to promises to async/await β€” makes you a much stronger Node developer.

ANALOGY

A restaurant kitchen, not a food stall. A food stall serves one customer at a time: take order, cook, serve, then next. A restaurant kitchen takes many orders, cooks them in parallel, and serves when each is ready. Node.js is the kitchen β€” it starts many I/O operations and handles results as they come in, without waiting for each to finish before starting the next.

Callback hell β†’ Promises β†’ async/await
javascript
// 1. CALLBACKS β€” the original approach (hard to read, error-prone)
fs.readFile("users.json", "utf8", (err, data) => {
  if (err) return handleError(err);
  const users = JSON.parse(data);
  db.save(users, (err, result) => {
    if (err) return handleError(err);
    sendEmail(result.ids, (err) => {
      if (err) return handleError(err);
      console.log("Done!");
    });
  });
});
// The "pyramid of doom" β€” each callback indents further

// 2. PROMISES β€” flattened with .then() chains
readFilePromise("users.json", "utf8")
  .then(data => JSON.parse(data))
  .then(users => db.save(users))
  .then(result => sendEmail(result.ids))
  .then(() => console.log("Done!"))
  .catch(err => handleError(err));

// 3. ASYNC/AWAIT β€” reads like synchronous code
async function processUsers() {
  try {
    const data  = await fs.promises.readFile("users.json", "utf8");
    const users = JSON.parse(data);
    const result = await db.save(users);
    await sendEmail(result.ids);
    console.log("Done!");
  } catch (err) {
    handleError(err);
  }
}
Running async operations in parallel
javascript
// Sequential β€” each waits for the previous (slow)
const users   = await fetchUsers();
const courses = await fetchCourses();
const stats   = await fetchStats();
// Total time = time(users) + time(courses) + time(stats)

// Parallel β€” all fire at once (fast)
const [users, courses, stats] = await Promise.all([
  fetchUsers(),
  fetchCourses(),
  fetchStats(),
]);
// Total time = max(time(users), time(courses), time(stats))

// Promise.allSettled β€” runs all, even if some fail
const results = await Promise.allSettled([fetchA(), fetchB()]);
results.forEach(r => {
  if (r.status === "fulfilled") use(r.value);
  else console.error(r.reason);
});