Callbacks & Error-First Pattern
📖 Concept
Callbacks are the original async pattern in Node.js. A callback is a function passed as an argument to another function, invoked when the operation completes.
The error-first callback convention:
Node.js follows a strict convention: the callback's first argument is always the error (or null if no error). This is called the error-first or Node-style callback pattern.
fs.readFile('file.txt', (error, data) => {
if (error) {
// Handle error
return;
}
// Use data
});
Why error-first?
- Consistent API across all Node.js core modules
- Forces developers to handle errors before processing data
- Enables tools and libraries to automatically wrap callbacks
The callback problem — "Callback Hell" (Pyramid of Doom): When you chain async operations, callbacks nest deeper and deeper:
getUser(id, (err, user) => {
getOrders(user.id, (err, orders) => {
getOrderDetails(orders[0].id, (err, details) => {
// 3 levels deep... and growing
});
});
});
Solutions to callback hell:
- Named functions — extract each callback into a named function
- Promises — chain with
.then()instead of nesting - async/await — write async code that looks synchronous
- Libraries —
async.jsprovides utilities likeasync.waterfall,async.parallel
When callbacks are still used:
- Legacy Node.js APIs (before Promises were standard)
- Event handlers (
EventEmitter,Stream) - Performance-critical code (callbacks have slightly less overhead than Promises)
- Third-party libraries that haven't migrated to Promises
🏠 Real-world analogy: A callback is like leaving your phone number with a restaurant when the table isn't ready — they "call back" when it's your turn. Error-first is like the restaurant saying "Sorry, we're full" before giving you a table number.
💻 Code Example
1// Callbacks — The Foundation of Node.js Async23const fs = require("fs");4const path = require("path");56// 1. Basic error-first callback7fs.readFile(path.join(__dirname, "example.txt"), "utf-8", (err, data) => {8 if (err) {9 if (err.code === "ENOENT") {10 console.log("File not found");11 } else {12 console.error("Read error:", err.message);13 }14 return; // ← Always return after handling errors!15 }16 console.log("File content:", data);17});1819// 2. Callback hell — ❌ BAD20function getUserDataBad(userId) {21 getUser(userId, (err, user) => {22 if (err) return console.error(err);23 getOrders(user.id, (err, orders) => {24 if (err) return console.error(err);25 getShippingStatus(orders[0].id, (err, status) => {26 if (err) return console.error(err);27 console.log(`User: ${user.name}, Status: ${status}`);28 // More nesting? This becomes unreadable...29 });30 });31 });32}3334// 3. Named functions — ✅ Flattened callbacks35function getUserDataGood(userId) {36 getUser(userId, handleUser);37}3839function handleUser(err, user) {40 if (err) return console.error(err);41 getOrders(user.id, (err, orders) => handleOrders(err, orders, user));42}4344function handleOrders(err, orders, user) {45 if (err) return console.error(err);46 console.log(`User ${user.name} has ${orders.length} orders`);47}4849// 4. Creating callback-based functions50function readJsonFile(filePath, callback) {51 fs.readFile(filePath, "utf-8", (err, data) => {52 if (err) return callback(err, null);5354 try {55 const json = JSON.parse(data);56 callback(null, json); // Success: error is null57 } catch (parseErr) {58 callback(new Error(`Invalid JSON in ${filePath}: ${parseErr.message}`), null);59 }60 });61}6263// Usage64readJsonFile("config.json", (err, config) => {65 if (err) {66 console.error("Failed:", err.message);67 return;68 }69 console.log("Config loaded:", config);70});7172// 5. Parallel execution with callbacks (manual)73function fetchAllData(callback) {74 let completed = 0;75 const results = {};76 const errors = [];7778 function checkDone() {79 completed++;80 if (completed === 3) {81 if (errors.length > 0) return callback(errors[0], null);82 callback(null, results);83 }84 }8586 fs.readFile("users.json", "utf-8", (err, data) => {87 if (err) errors.push(err);88 else results.users = JSON.parse(data);89 checkDone();90 });9192 fs.readFile("posts.json", "utf-8", (err, data) => {93 if (err) errors.push(err);94 else results.posts = JSON.parse(data);95 checkDone();96 });9798 fs.readFile("comments.json", "utf-8", (err, data) => {99 if (err) errors.push(err);100 else results.comments = JSON.parse(data);101 checkDone();102 });103}104105// 6. Converting callbacks to Promises106const { promisify } = require("util");107const readFileAsync = promisify(fs.readFile);108// Now: const data = await readFileAsync("file.txt", "utf-8");109110// Mock functions for examples above111function getUser(id, cb) { cb(null, { id, name: "Alice" }); }112function getOrders(userId, cb) { cb(null, [{ id: 1 }, { id: 2 }]); }113function getShippingStatus(orderId, cb) { cb(null, "shipped"); }
🏋️ Practice Exercise
Exercises:
- Write a callback-based function that reads a directory and returns only
.jsfiles - Create a
readJsonFilefunction with proper error-first callback handling - Chain 3 file operations using callbacks — then refactor to eliminate the nesting
- Implement a parallel file reader that reads 5 files concurrently and returns results in order
- Use
util.promisify()to convert 3 callback-based Node.js APIs to Promise-based ones - Build a retry function that attempts a callback operation up to 3 times before failing
⚠️ Common Mistakes
Forgetting to
returnafter calling the callback with an error — the function continues executing and may call the callback twiceNot following the error-first convention — putting data as the first argument confuses everyone and breaks
util.promisify()Calling a callback synchronously in some code paths and asynchronously in others — this creates unpredictable behavior known as 'Zalgo'
Throwing errors inside callbacks instead of passing them — thrown errors crash the process because there's no try/catch wrapping the async call
Not realizing that callbacks in Node.js core APIs run in the next event loop tick — code after the async call runs BEFORE the callback
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for Callbacks & Error-First Pattern. Login to unlock this feature.