Node.js6 min read

Understanding Callbacks in Node.js

Master callback patterns in Node.js. Understand error-first callbacks and avoid callback hell.

Sarah Chen
December 19, 2025
0.0k0

Understanding Callbacks in Node.js

Callbacks are functions passed as arguments. Node.js uses them everywhere.

What's a Callback?

function greet(name, callback) {
  const message = `Hello, ${name}!`;
  callback(message);
}

greet('Alice', (msg) => {
  console.log(msg);  // Hello, Alice!
});

Why Callbacks?

Node.js is non-blocking. Instead of waiting:

// Blocking (bad in Node.js)
const data = readFileSync('file.txt');  // Waits here
console.log(data);

// Non-blocking (good)
readFile('file.txt', (err, data) => {
  console.log(data);  // Runs when file is read
});
console.log('This runs immediately!');

Error-First Callbacks

Node.js convention: first argument is always error.

const fs = require('fs');

fs.readFile('file.txt', 'utf8', (err, data) => {
  if (err) {
    console.error('Error:', err.message);
    return;
  }
  console.log('Data:', data);
});

Creating Error-First Callbacks

function divide(a, b, callback) {
  if (b === 0) {
    callback(new Error('Cannot divide by zero'));
    return;
  }
  callback(null, a / b);
}

divide(10, 2, (err, result) => {
  if (err) {
    console.error(err.message);
    return;
  }
  console.log('Result:', result);  // 5
});

Callback Hell (The Problem)

// Nested callbacks = hard to read
fs.readFile('file1.txt', (err, data1) => {
  if (err) return handleError(err);
  
  fs.readFile('file2.txt', (err, data2) => {
    if (err) return handleError(err);
    
    fs.readFile('file3.txt', (err, data3) => {
      if (err) return handleError(err);
      
      // Finally do something with data1, data2, data3
      console.log(data1, data2, data3);
    });
  });
});

Avoiding Callback Hell

1. Named functions:

function handleFile1(err, data1) {
  if (err) return handleError(err);
  fs.readFile('file2.txt', handleFile2);
}

function handleFile2(err, data2) {
  if (err) return handleError(err);
  console.log('Done!');
}

fs.readFile('file1.txt', handleFile1);

2. Use Promises (better):

const fs = require('fs').promises;

async function readFiles() {
  const data1 = await fs.readFile('file1.txt');
  const data2 = await fs.readFile('file2.txt');
  const data3 = await fs.readFile('file3.txt');
  return [data1, data2, data3];
}

Convert Callback to Promise

const { promisify } = require('util');

// Old callback style
const readFile = promisify(fs.readFile);

// Now use with async/await
const data = await readFile('file.txt', 'utf8');

Key Takeaway

Callbacks are foundational in Node.js. Always handle errors first. Avoid deep nesting with named functions or better yet, use Promises and async/await for cleaner code.

#Node.js#Callbacks#Async#Beginner