Node.js callback function for asynchronous programming
A Node.js callback function is a function passed as an argument to another function and called later when an operation finishes. Callbacks are widely used in Node.js because many tasks, such as reading files, querying databases, or handling network requests, do not complete immediately.
With a callback, Node.js can start an operation and continue running the next statements instead of waiting for the operation to finish. When the operation completes, Node.js invokes the callback with the result or an error. This pattern is one of the basic ways to write asynchronous JavaScript in Node.js.
This tutorial explains callback functions in Node.js using file system examples, error-first callbacks, nested callbacks, and the difference between callbacks, events, and promises.
- What a Node.js callback function does
- Node.js callback function syntax
- Blocking function example with readFileSync()
- Asynchronous Node.js callback function example with readFile()
- Error-first callback pattern in Node.js
- Nested callback function example in Node.js
- Difference between callback and event in Node.js
- Difference between callback and promise in Node.js
- Node.js callback function FAQs
What a Node.js callback function does
A callback function lets one function decide what should happen after its work is complete. The function that receives the callback controls when to call it. The callback contains the code that should run later.
In Node.js, callbacks are especially common in APIs that perform input/output work. For example, fs.readFile() starts reading a file and accepts a callback that runs after the file operation completes. The official Node.js learning material also introduces callbacks as a core part of asynchronous JavaScript in Node.js: JavaScript asynchronous programming and callbacks.
A very small callback example is shown below. The sayHello function is passed into greetUser and called from inside it.
function greetUser(name, callback) {
callback(name);
}
function sayHello(name) {
console.log('Hello ' + name);
}
greetUser('Arjun', sayHello);
Output
Hello Arjun
Here, sayHello is not called when it is passed. It is called later from inside greetUser. That is the basic idea behind a callback.
Node.js callback function syntax
The callback syntax is simple: pass a function as an argument, and call that function when the work is done. In many Node.js APIs, the callback receives an error as the first argument and the result as the second argument.
someAsyncOperation(input, function (err, result) {
if (err) {
// handle error
return;
}
// use result
});
This is often called the error-first callback pattern. If the operation fails, err contains error information. If the operation succeeds, err is usually null or undefined, and the result argument contains the data.
Example 1 – Blocking Function
In this example, we will read a file “sample.txt” synchronously using readFileSync() method.
read-file-sync.js
var fs = require('fs');
// read file sample.txt
var data = fs.readFileSync('sample.txt');
console.log("Reading file completed : " + new Date().toISOString());
console.log("After readFileSync statement : " + new Date().toISOString());
Run this program using node in command-prompt/terminal, and you will get the following output.
Output
arjun@arjun-VPCEH26EN:~/nodejs$ node read-file-sync.js
Reading file completed : 2017-10-19T12:21:40.103Z
After readFileSync statement : 2017-10-19T12:21:40.105Z
The After readFileSync statement is always executed only after reading the file is completed. fs.readFileSync is blocking the execution flow.
Use synchronous methods carefully in server-side request handling because they block the current JavaScript execution while the operation is running. Synchronous methods can still be useful in short scripts, startup configuration, or command-line utilities where the blocking behavior is acceptable.
Example 2 – Node.js Callback Function
Following is an example node script which reads “sample.txt” file asynchronously, with the help of Node.js Callback Function.
read-file-async.js
var fs = require('fs');
// read file sample.txt
fs.readFile('sample.txt',
// callback function that is called when reading file is done
function(err, data) {
if (err) throw err;
// data is a buffer containing file content
console.log("Reading file completed : " + new Date().toISOString());
});
console.log("After readFile asynchronously : " + new Date().toISOString());
Run this program using node in command-prompt/terminal, and you will get the following output.
Output
arjun@arjun-VPCEH26EN:~/nodejs$ node read-file-async.js
After readFile asynchronously : 2017-10-19T12:25:36.982Z
Reading file completed : 2017-10-19T12:25:36.987Z
You may observe that even after executing console.log(“After readFile asynchronously statement, it took around 5ms to complete reading the file. fs.readFile(‘sample.txt’, callback function{..}) has not blocked the execution, instead a new process is started in parallel with the main control flow, to read the file (perform the task on resource).
More accurately, asynchronous file work is scheduled and the callback is called later when the result is available. Node.js does not pause the main JavaScript flow at the fs.readFile() line. That is why the second log statement can appear before the file-reading log statement.
Error-first callback pattern in Node.js
The previous callback example uses if (err) throw err;. That is enough for a small demo, but production code usually handles the error inside the callback or passes it to a higher-level error handler. Throwing inside an asynchronous callback can terminate the process if it is not handled elsewhere.
The following version checks the error, prints a useful message, and returns early so the success code does not run when the file read fails.
read-file-error-first-callback.js
const fs = require('fs');
fs.readFile('sample.txt', 'utf8', function (err, data) {
if (err) {
console.log('Unable to read file:', err.code);
return;
}
console.log('File contents:');
console.log(data);
});
console.log('Read operation started');
Output when sample.txt is missing
Read operation started
Unable to read file: ENOENT
This pattern makes the callback easier to read: handle the error first, return, and then write the success logic below it.
Example 3 – Node.js Nested Callback Function
To demonstrate Node.js Nested Callback Function, we shall consider a scenario of renaming a file and then deleting it using asynchronous functions.
nodejs-nested-callback.js
var fs = require('fs');
fs.rename('sample.txt', 'sample_old.txt',
// 1st call back function
function (err) {
if (err) throw err;
console.log('File Renamed.');
fs.unlink('sample_old.txt',
// 2nd call back function
function (err) {
if (err) throw err;
console.log('File Deleted.');
}
);
}
);
Run this program using node in command-prompt/terminal, and you will get the following output.
Output
arjun@arjun-VPCEH26EN:~/nodejs$ node nodejs-nested-callback.js
File Renamed.
File Deleted.
Nested callbacks are useful when the second asynchronous operation must start only after the first one succeeds. In the example above, the file is deleted only after it is renamed.
However, deeply nested callbacks can become hard to read and maintain. This problem is often called callback nesting or callback hell. When the sequence becomes long, consider named callback functions, promises, or async/await.
Reducing nested Node.js callbacks with named functions
One simple way to make nested callbacks clearer is to move each callback into a named function. The code still uses callbacks, but the flow is easier to scan.
nodejs-named-callbacks.js
const fs = require('fs');
function deleteRenamedFile(err) {
if (err) {
console.log('Rename failed:', err.code);
return;
}
console.log('File renamed.');
fs.unlink('sample_old.txt', function (err) {
if (err) {
console.log('Delete failed:', err.code);
return;
}
console.log('File deleted.');
});
}
fs.rename('sample.txt', 'sample_old.txt', deleteRenamedFile);
This style is still callback-based, but it avoids placing all logic inside one deeply indented block.
Difference between callback and event in Node.js
A callback is usually passed to one operation and called when that operation finishes. An event is emitted by an object, and one or more listeners can react to it. Events are useful when something can happen multiple times, such as receiving data from a stream or handling repeated user actions.
| Feature | Node.js callback | Node.js event |
|---|---|---|
| How it is registered | Passed as a function argument | Registered with an event listener such as on() |
| Typical use | Completion of a single async operation | Repeated or named occurrences |
| Example | fs.readFile(path, callback) | stream.on('data', listener) |
| Number of handlers | Usually one callback per operation | Multiple listeners can react to the same event |
The following example shows a basic event listener. This is not the same as passing a callback to fs.readFile(), although both use functions.
const EventEmitter = require('events');
const order = new EventEmitter();
order.on('paid', function (orderId) {
console.log('Payment received for order:', orderId);
});
order.emit('paid', 101);
Output
Payment received for order: 101
Difference between callback and promise in Node.js
Callbacks and promises both handle asynchronous results, but they organize code differently. A callback receives the result through a function argument. A promise represents a future result and lets you attach .then() and .catch() handlers, or use await inside an async function.
| Feature | Callback | Promise |
|---|---|---|
| Error handling | Usually err as first callback argument | .catch() or try...catch with await |
| Readability for long chains | Can become nested | Can be chained or written with async/await |
| Result delivery | Callback is called with result arguments | Promise resolves with a value or rejects with an error |
| Common Node.js examples | fs.readFile() | fs.promises.readFile() |
The same file read can be written using async/await with promise-based file system APIs.
read-file-async-await.js
const fs = require('fs/promises');
async function readSampleFile() {
try {
const data = await fs.readFile('sample.txt', 'utf8');
console.log(data);
} catch (err) {
console.log('Unable to read file:', err.code);
}
}
readSampleFile();
Callbacks are still important because many Node.js APIs and older codebases use them. Promises and async/await are often easier for longer asynchronous flows.
Common mistakes with Node.js callback functions
- Ignoring the error argument. Always check
errbefore using the data returned to the callback. - Calling the callback more than once. A completion callback should normally be called once for one operation.
- Forgetting to return after handling an error. Without
return, the success code may continue running. - Assuming callback code runs immediately. Asynchronous callbacks run later, after the current call stack finishes.
- Nesting too many callbacks. Use named functions, promises, or
async/awaitwhen the flow becomes difficult to follow.
Node.js callback function FAQs
What does callback() do in JavaScript and Node.js?
callback() calls the function that was passed as an argument. In Node.js, this is commonly done after an asynchronous operation finishes, so the callback can receive an error or result data.
What is a simple example of a Node.js callback function?
fs.readFile('sample.txt', function (err, data) { ... }) is a common example. Node.js starts reading the file and later calls the function with either an error or the file contents.
What is the difference between an event and a callback in Node.js?
A callback is passed to one operation and is usually called when that operation completes. An event is emitted by an object, and listeners registered with methods like on() can react whenever that event occurs.
What is the difference between callback and promise in Node.js?
A callback receives the asynchronous result through a function call. A promise represents a future result and can be handled with .then(), .catch(), or async/await.
Should I throw errors inside a Node.js callback?
For small examples, you may see if (err) throw err;. In application code, it is usually safer to handle the error inside the callback or pass it to a central error handler instead of throwing from an asynchronous callback.
QA checklist for reviewing Node.js callback code
- Confirm that every callback checks the
errargument before using result data. - Check that the callback is not accidentally called more than once for the same operation.
- Verify that error branches use
returnwhen the success logic should not continue. - Review nested callbacks for readability and consider named functions or
async/awaitfor long sequences. - Confirm that asynchronous output order is explained correctly and not described as strictly top-to-bottom execution.
- Use callback examples for callback APIs and promise examples for promise APIs, without mixing the two patterns unnecessarily.
Node.js callback function key takeaways
In this Node.js Tutorial – Node.js Callback Function, we have learnt the execution flow of Callback functions and how they are non-blocking, and also how to use nested callback functions with examples.
A Node.js callback function is useful when code needs to run after an asynchronous operation finishes. For callback-based APIs, use the error-first callback pattern, handle err before reading data, and avoid deep nesting when the flow grows. For newer code, callbacks remain important to understand, while promises and async/await provide another way to structure asynchronous operations.
TutorialKart.com