Module F-4·22 min read

The callback pattern, callback hell, Promises, async/await, Promise.all — and a first look at why Node.js never blocks, without the deep internals.

JJS
Written by Jatin Jain Saraf · Senior Software Engineer

Module F-4 — Async JavaScript: Callbacks, Promises, and the Event Loop Intro

What this module covers: Asynchronous programming is the single hardest concept for developers coming to Node.js. Not because the ideas are complicated — they aren't — but because Node.js inherited a pile of historical patterns (callbacks, then Promises, then async/await) and you will encounter all three in the wild. This module explains all of them, why each one exists, and how to think about the event loop without needing to know its internals yet. The deep event loop mechanics are in Phase 3. Here you get the mental model that makes everything else in Phase 1 and 2 work.


Why Asynchronous Programming Exists

Consider this: you ask a database for some rows. On a fast network, the database responds in 1–5 milliseconds. In that 1–5ms, your CPU is not computing anything — it is idle, waiting for the network packet to arrive.

In a traditional synchronous program (or a thread-per-request server), that thread just sits there blocked. It cannot handle anything else while it waits. If 100 requests all arrive at once and each takes 5ms of I/O, you need 100 threads running simultaneously.

Node.js takes a different approach: register a callback or a Promise, then go do other work while waiting. When the I/O completes, execute the registered function. One thread. No waiting. This is the core idea.

You do not need to understand how the event loop implements this at a low level right now — that is Phase 3. What you need to understand now is the programming model: how you write code that does not block.


The Callback Pattern

Callbacks are the original mechanism for async in Node.js. A callback is simply a function you pass as an argument, to be called when some work is done.

Analogy: A callback is a claim ticket at a dry cleaner — you hand off the work and walk away, and the shop calls your number when it's done instead of making you stand at the counter.

javascript

Output:

text

The key thing: code after fs.readFile() does not wait for the file. Node.js fires off the file read, moves on, and calls your callback later when the data is ready.

The error-first convention: Node.js callbacks always receive (err, result). If err is not null, something went wrong. Always check it first.

javascript

The Problem: Callback Hell

Callbacks work fine for a single operation. Problems start when you need to chain operations — do this, then that, then the other thing:

javascript

This rightward drift — each operation nesting inside the previous one — is called callback hell or the pyramid of doom. It is:

  • Hard to read
  • Hard to maintain
  • Hard to add error handling to each level
  • Impossible to easily add parallel operations

Promises were invented to solve this.


Promises

A Promise is an object that represents the eventual result of an asynchronous operation. It is either:

  • Pending — the operation is still in progress
  • Fulfilled — the operation completed successfully, and the Promise has a value
  • Rejected — the operation failed, and the Promise has a reason (an error)
javascript

Using a Promise with .then() and .catch()

javascript

The key improvement over callbacks: errors flow to a single .catch() instead of needing to be checked at every level. And .then() chains are flat rather than nested.

Chaining Promises (the right way)

javascript

Flat. Readable. One .catch() handles any failure in the chain.

The Classic Bug: Forgetting to return Inside .then()

Sign in to keep reading

The rest of this module is free — sign in with Google to unlock it and track your progress.

Sign in & Register

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.