Callbacks are functions passed as arguments to other functions. They are the foundation of async programming in Node.js. Before Promises and async/await, callbacks were the only way to handle async operations.
Basic Callback Pattern
function fetchData(callback) {
setTimeout(() => {
const data = { id: 1, name: "Alice" };
callback(null, data);
}, 1000);
}
fetchData((error, data) => {
if (error) {
console.error("Error:", error);
return;
}
console.log("Data:", data);
});
The convention is: the first argument is always an error (or null if success), the second is the data.
The Error-First Pattern
This is the universal callback convention in Node.js:
fs.readFile("data.txt", (err, data) => {
if (err) {
console.error("Failed to read file:", err);
return;
}
console.log("File content:", data.toString());
});
Always check for errors first. This pattern makes error handling explicit and consistent.
Try it Yourself โCallback Hell
When you nest too many callbacks, code becomes unreadable:
// Don't do this
getUser(id, (err, user) => {
getOrders(user.id, (err, orders) => {
getOrderDetails(orders[0].id, (err, details) => {
sendEmail(user.email, details, (err, result) => {
console.log("Done!", result);
});
});
});
});
This is called "callback hell" โ the code drifts to the right and becomes impossible to read. This is why Promises and async/await were created.
Key Takeaway: Callbacks are still used in Node.js, especially for event handlers and low-level APIs. But for async operations, prefer Promises or async/await to avoid callback hell.