devnotes.

The Callback Concept

A callback is simply a function passed as an argument to another function, to be called once that function's job is done. It's the original, most basic tool Node.js gives you for handling something asynchronous.

A callback you've already been using

fs.readFile('notes.txt', 'utf8', (err, data) => {
  console.log(data);
});

That arrow function is the callback. You're not calling it — readFile is. You're just handing it over, and readFile decides when it's ready to invoke it.

The error-first pattern

Almost every core Node.js callback follows the same shape: the first parameter is always reserved for an error (or null if nothing went wrong), and the actual result comes after it.

fs.readFile('config.json', 'utf8', (err, data) => {
  if (err) {
    console.error('Could not read config:', err.message);
    return; // stop here — don't try to use `data`, it doesn't exist
  }
  console.log('Config loaded:', data);
});

Checking err first, before touching anything else, is the single habit that prevents the most common beginner bug: crashing because you tried to use a result that never arrived.

Writing your own callback-based function

function validateAge(age, callback) {
  setTimeout(() => {
    if (age < 18) {
      callback(new Error('Must be 18 or older'));
    } else {
      callback(null, 'Age verified');
    }
  }, 500); // simulating a slow check, e.g. an external service
}

validateAge(20, (err, result) => {
  if (err) return console.error(err.message);
  console.log(result);
});

This mimics what a real callback-based library does internally — delay a response, then call back with either an error or a result.

Where callbacks start to hurt: callback hell

Once you need several async steps in sequence, each depending on the last, nesting callbacks inside callbacks gets ugly fast:

getUser(id, (err, user) => {
  getOrders(user.id, (err, orders) => {
    getInvoice(orders[0].id, (err, invoice) => {
      sendEmail(user.email, invoice, (err, result) => {
        console.log('Done, but look at this shape...');
      });
    });
  });
});

Each step drifts one level further right, error handling is scattered across four different places, and reading the actual order of operations means scanning diagonally down the page. This exact pain point is what Promises and async/await were introduced to solve — covered next.

The Callback Concept | DevNotes