- Node.js Tutorial
- NodeJS Home
- NodeJS Introduction
- NodeJS Setup
- NodeJS First App
- NodeJS REPL
- NodeJS Command Line
- NodeJS NPM
- NodeJS Callbacks
- NodeJS Events
- NodeJS Event-Loop
- NodeJS Event-Emitter
- NodeJS Global-Objects
- NodeJS Console
- NodeJS Process
- NodeJS Buffers
- NodeJS Streams
- Node.js File Handling
- Node.js File System
- Node.js Read/Write File
- Working with folders in Node.js
- HTTP and Networking
- Node.js HTTP Module
- Anatomy of an HTTP Transaction
- Node.js MongoDB
- MongoDB Get Started
- MongoDB Create Database
- MongoDB Create Collection
- MongoDB Insert
- MongoDB Find
- MongoDB Query
- MongoDB Sort
- MongoDB Delete
- MongoDB Update
- MongoDB Limit
- MongoDB Join
- Node.js MySQL
- MySQL Get Started
- MySQL Create Database
- MySQL Create Table
- MySQL Insert Into
- MySQL Select From
- MySQL Where
- MySQL Order By
- MySQL Delete
- MySQL Update
- MySQL Join
- Node.js Modules
- Node.js Modules
- Node.js Built-in Modules
- Node.js Utility Modules
- Node.js Web Module
- Node.js Advanced
- Node.js Debugger
- Node.js Scaling Application
- Node.js Packaging
- Node.js Express Framework
- Node.js RESTFul API
- Node.js Useful Resources
- Node.js Useful Resources
- Node.js Discussion
Node.js Callbacks
In Node.js, callbacks are the backbone of asynchronous programming. Because Node.js runs on a single-threaded event loop, it cannot afford to wait for a slow taskālike reading a large file or querying a busy databaseāto finish. Instead, it starts the task, moves on to the next line of code, and executes a callback function once the task is complete.
Key Features of Callbacks
- Asynchronous Execution: They prevent the application from "freezing" while waiting for Input/Output (I/O) operations.
- Control Flow: They define exactly what should happen once a specific piece of data becomes available.
- Error Handling: Node.js utilizes a standard pattern to ensure errors are caught and handled before they crash your app.
Structure of a Callback
A callback is simply a function passed as an argument to another function. While we usually use them for asynchronous tasks, you can see the logic in a simple synchronous example.
Example: Synchronous Callback
function greet(name, callback) {
console.log(`Hello, ${name}`);
callback();
}
function sayGoodbye() {
console.log('Goodbye!');
}
// sayGoodbye is passed as the 'callback' argument
greet('Alice', sayGoodbye);
Output:
Hello, Alice
Goodbye!
Using Callbacks in Node.js
1. Reading Files (Asynchronous)
The fs (File System) module is the most common place where beginners encounter callbacks. Instead of stopping everything to read a file, Node.js reads it in the background.
const fs = require('fs');
// The third argument is our callback function
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) {
console.error('Something went wrong:', err.message);
return;
}
console.log('File content successfully loaded:', data);
});
fs.readFile, Node.js will return a Buffer (raw binary data) instead of a readable string.
2. Error-First Callback Pattern
Almost all Node.js core APIs follow the Error-First Callback convention. In this pattern, the first argument of the callback is reserved for an error object. If the operation succeeds, the first argument is null or undefined, and the subsequent arguments contain the result data.
function getUserFromDatabase(id, callback) {
// Simulating a database delay
setTimeout(() => {
const databaseDown = false;
if (databaseDown) {
callback(new Error('Could not connect to DB'), null);
} else {
const user = { id: id, username: 'dev_hero' };
callback(null, user);
}
}, 1000);
}
getUserFromDatabase(101, (err, user) => {
if (err) {
return console.error('Error:', err.message);
}
console.log('User found:', user.username);
});
return inside the if (err) block. If you don't return, the rest of the function will continue to execute, often leading to "Cannot read property of null" errors.
Callback Hell
As your application grows, you might need to perform several asynchronous tasks in a specific order (e.g., login a user, get their profile, then fetch their posts). When you nest callbacks inside callbacks, you end up with Callback Hell (also known as the "Pyramid of Doom").
fs.readFile('config.json', 'utf8', (err, config) => {
if (err) return console.error(err);
const dbUrl = JSON.parse(config).url;
connectToDb(dbUrl, (err, db) => {
if (err) return console.error(err);
db.query('SELECT * FROM users', (err, users) => {
if (err) return console.error(err);
console.log('Users retrieved:', users);
// It keeps going deeper...
});
});
});
This code is difficult to debug, hard to test, and visually messy.
Alternatives to Callbacks
Modern Node.js development has moved toward cleaner ways of handling asynchrony, but these still rely on callbacks under the hood.
1. Promises
Promises represent a value that will be available "eventually." They allow you to chain operations using .then() and catch all errors in a single .catch() block.
const fsPromises = require('fs').promises;
fsPromises.readFile('file1.txt', 'utf8')
.then(data => console.log('File 1:', data))
.then(() => fsPromises.readFile('file2.txt', 'utf8'))
.then(data => console.log('File 2:', data))
.catch(err => console.error('An error occurred:', err));
2. Async/Await
Introduced in ES2017, async/await allows you to write asynchronous code that looks and behaves like synchronous code, making it much easier to read.
async function getFileData() {
try {
const data1 = await fsPromises.readFile('file1.txt', 'utf8');
const data2 = await fsPromises.readFile('file2.txt', 'utf8');
console.log('Combined data:', data1, data2);
} catch (err) {
console.error('Fetch failed:', err);
}
}
getFileData();
util.promisify() method in Node.js.
Summary
Callbacks are the foundation of Node.js. They enable the non-blocking I/O that makes Node.js fast and scalable. While the error-first pattern is a standard you must know, you should strive to use Promises and Async/Await in modern projects to keep your code clean, readable, and maintainable.