JavaScript Callbacks

A callback is a function passed as an argument to another function. The receiving function can then call (invoke) the callback at some point during its execution. Callbacks are the foundation of asynchronous programming in JavaScript.

What Are Callbacks?

A callback function is simply a function that is passed to another function as a parameter and is invoked inside that function. Callbacks allow you to control when a function runs.

Basic Callback
function greet(name, callback) {
  console.log('Hello, ' + name + '!');
  callback();
}

function sayGoodbye() {
  console.log('Goodbye!');
}

greet('Alice', sayGoodbye);

Callback Functions in Practice

Callbacks are used extensively in JavaScript. Array methods like forEach, map, and filter all use callbacks. Event listeners also use callbacks.

Callbacks with Array Methods
const numbers = [1, 2, 3, 4, 5];

// forEach with callback
numbers.forEach(function(num) {
  console.log('Number: ' + num);
});

// map with callback
const doubled = numbers.map(function(num) {
  return num * 2;
});
console.log('Doubled: ' + doubled);

// filter with callback
const evens = numbers.filter(function(num) {
  return num % 2 === 0;
});
console.log('Evens: ' + evens);

setTimeout and setInterval

setTimeout() calls a callback function once after a specified delay. setInterval() calls a callback function repeatedly at specified intervals.

setTimeout and setInterval
// setTimeout - runs once after delay
console.log('Before timeout');
setTimeout(function() {
  console.log('This runs after 1 second');
}, 1000);

// setInterval example (conceptual)
let count = 0;
const intervalId = setInterval(function() {
  count++;
  console.log('Tick: ' + count);
  if (count >= 3) {
    clearInterval(intervalId);
    console.log('Interval cleared');
  }
}, 500);

Callback Hell

When callbacks are nested inside callbacks, the code becomes deeply indented and hard to read. This pattern is known as 'callback hell' or the 'pyramid of doom'.

Callback Hell Example
// Simulating callback hell
function step1(callback) {
  setTimeout(function() {
    console.log('Step 1 complete');
    callback();
  }, 100);
}

function step2(callback) {
  setTimeout(function() {
    console.log('Step 2 complete');
    callback();
  }, 100);
}

function step3(callback) {
  setTimeout(function() {
    console.log('Step 3 complete');
    callback();
  }, 100);
}

// Nested callbacks (pyramid of doom)
step1(function() {
  step2(function() {
    step3(function() {
      console.log('All steps done!');
    });
  });
});

Error-First Callbacks

A common pattern in Node.js is the error-first callback, where the first argument of the callback is reserved for an error object. If no error occurred, the first argument is null.

Error-First Callback Pattern
function fetchData(id, callback) {
  if (id <= 0) {
    callback(new Error('Invalid ID'), null);
    return;
  }
  // Simulate successful data fetch
  const data = { id: id, name: 'Item ' + id };
  callback(null, data);
}

// Using error-first callback
fetchData(1, function(err, data) {
  if (err) {
    console.log('Error: ' + err.message);
    return;
  }
  console.log('Success: ' + JSON.stringify(data));
});

fetchData(-1, function(err, data) {
  if (err) {
    console.log('Error: ' + err.message);
    return;
  }
  console.log('Success: ' + JSON.stringify(data));
});
Problem with CallbacksDescription
Callback HellDeeply nested callbacks are hard to read
Inversion of ControlYou trust another function to call your callback
Error HandlingNo standard way (error-first is a convention)
No return valueCallbacks don't return values to the caller
Hard to composeDifficult to combine multiple async operations
📝 Note: Callbacks are still widely used in JavaScript, especially for event handlers and simple asynchronous operations. However, for complex async flows, Promises and async/await are preferred as they solve many of the problems associated with callbacks.
Exercise:
What is 'callback hell'?
Try it YourselfCtrl+Enter to run
Click Run to see the output here.