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

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.

</>
Copy
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.

</>
Copy
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

</>
Copy
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

</>
Copy
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

</>
Copy
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

</>
Copy
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

</>
Copy
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.

FeatureNode.js callbackNode.js event
How it is registeredPassed as a function argumentRegistered with an event listener such as on()
Typical useCompletion of a single async operationRepeated or named occurrences
Examplefs.readFile(path, callback)stream.on('data', listener)
Number of handlersUsually one callback per operationMultiple 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.

</>
Copy
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.

FeatureCallbackPromise
Error handlingUsually err as first callback argument.catch() or try...catch with await
Readability for long chainsCan become nestedCan be chained or written with async/await
Result deliveryCallback is called with result argumentsPromise resolves with a value or rejects with an error
Common Node.js examplesfs.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

</>
Copy
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 err before 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/await when 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 err argument before using result data.
  • Check that the callback is not accidentally called more than once for the same operation.
  • Verify that error branches use return when the success logic should not continue.
  • Review nested callbacks for readability and consider named functions or async/await for 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.