Error-First Callbacks
Node's classic async pattern passes an error as the first argument.
Syntax
fn((err, data) => { if (err) ...; use(data); })Before promises, Node used callbacks for asynchronous work. Node follows the error-first convention: the callback's first argument is an error (or null if all went well), and the result follows.
Always check the error
The first thing a callback should do is check whether err is set, and handle it before using the data.
Example
const fs = require('fs');
fs.readFile('data.txt', 'utf8', (err, data) => {
if (err) {
console.error('Failed:', err.message);
return;
}
console.log('Got data:', data);
});When to use it
- An older Express middleware uses the error-first callback pattern `(err, req, res, next)` to pass database errors up the middleware chain.
- A file-upload library accepts a `callback(err, filePath)` so callers can handle both success and failure in one function.
- A Node.js core `fs.readFile` call uses a callback to avoid blocking the event loop while disk I/O completes in the background.
More examples
Error-first callback pattern
Shows the Node.js error-first callback convention -- always check `err` before using the result.
const fs = require('fs');
fs.readFile('data.txt', 'utf8', (err, data) => {
if (err) {
console.error('Read failed:', err.message);
return; // early exit on error
}
console.log('Content:', data);
});Write your own callback function
Implements a custom error-first callback where `null` in the first argument signals success.
function divide(a, b, callback) {
if (b === 0) return callback(new Error('Cannot divide by zero'));
callback(null, a / b);
}
divide(10, 2, (err, result) => {
if (err) console.error(err.message);
else console.log('Result:', result); // 5
});Callback hell and why to avoid it
Illustrates callback hell -- deeply nested callbacks -- and why promises/async-await are preferred.
// Anti-pattern: deeply nested callbacks
fs.readFile('a.txt', 'utf8', (err, a) => {
fs.readFile('b.txt', 'utf8', (err, b) => {
fs.writeFile('c.txt', a + b, (err) => {
console.log('Done'); // hard to read and handle errors
});
});
});
// Prefer: fs/promises with async/await (see next lesson)
Discussion