Callbacks

Node's Original Async Style — Still Everywhere

Callbacks

Pass a function to be called when work finishes. Node's classic pattern — superseded by promises but still around.

3 min read Level 1/5 #nodejs#callbacks#async
What you'll learn
  • Recognize the (err, result) callback convention
  • Know why callbacks fell out of favor
  • Convert callbacks to promises with promisify

Before promises, Node used callbacks for everything async. You’d pass a function that the runtime calls when the work finishes.

The Pattern

import { readFile } from "node:fs";

readFile("data.txt", "utf8", (err, content) => {
  if (err) {
    console.error("read failed", err);
    return;
  }
  console.log(content);
});

Node’s convention: callback’s first argument is always the error (or null). Remaining args are the result. Same shape across the whole standard library.

The Problem — Nesting

Stack a few of them and you get the famous “callback pyramid”:

readFile("a.txt", "utf8", (err, a) => {
  if (err) return done(err);
  readFile("b.txt", "utf8", (err, b) => {
    if (err) return done(err);
    writeFile("c.txt", a + b, (err) => {
      if (err) return done(err);
      done(null, "all done");
    });
  });
});

Hard to read, hard to add error handling, hard to compose.

Convert to Promises

util.promisify wraps a Node-style callback function as a promise:

import { readFile, writeFile } from "node:fs";
import { promisify } from "node:util";

const read  = promisify(readFile);
const write = promisify(writeFile);

const a = await read("a.txt", "utf8");
const b = await read("b.txt", "utf8");
await write("c.txt", a + b);

For the standard library, just import from /promisesnode:fs/promises, node:dns/promises, node:timers/promises.

When You’ll Still See Callbacks

  • Old library APIs (request, mongojs, etc.)
  • Stream events (stream.on("data", ...))
  • EventEmitter listeners
  • Some Node APIs (e.g. crypto.scrypt — has both)

Knowing the pattern means you can read older code and convert it.

Promises →