0% found this document useful (0 votes)
5 views

Callbacks in JavaScript_ a Comprehensive Guide

Callbacks in JavaScript are functions passed as arguments to other functions, allowing for deferred execution and event handling. While they are essential for asynchronous programming, deeply nested callbacks can lead to 'callback hell', prompting the introduction of Promises and async/await for better readability and error handling. Understanding callbacks is crucial for working with JavaScript APIs and writing robust asynchronous code.

Uploaded by

Aadrsh mishra
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views

Callbacks in JavaScript_ a Comprehensive Guide

Callbacks in JavaScript are functions passed as arguments to other functions, allowing for deferred execution and event handling. While they are essential for asynchronous programming, deeply nested callbacks can lead to 'callback hell', prompting the introduction of Promises and async/await for better readability and error handling. Understanding callbacks is crucial for working with JavaScript APIs and writing robust asynchronous code.

Uploaded by

Aadrsh mishra
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 9

Callbacks in JavaScript: A Comprehensive Guide

Callbacks are fundamental to JavaScript. In simple terms, a callback is a function passed as an


argument to another function, which is then invoked (“called back”) inside the outer function to perform
some action 1 2 . This allows one function to defer execution of another until a later time or an event
occurs. For example, you might pass a display function into a calculator function, and let the calculator
“call back” the display when the result is ready 2 . The MDN Glossary defines a callback as “a function
passed into another function as an argument, which is then invoked inside the outer function to complete
some kind of routine or action.” 1

Basic Callback Syntax


In practice, callbacks are just functions. You can use named functions or anonymous functions
(including arrow functions) as callbacks. Here is a simple example:

function greet(name, callback) {


console.log("Hello, " + name + "!");
callback(); // Call the callback function
}

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

// Pass `sayGoodbye` as a callback to `greet`


greet("Alice", sayGoodbye);
// Output:
// Hello, Alice!
// Goodbye!

In this code, greet takes two parameters: name and callback . After logging a greeting, greet
calls callback() . We pass the function sayGoodbye as the callback, so after printing “Hello, Alice!”,
the program prints “Goodbye!”.

Notice how the callback function is not executed immediately when passed, but is stored and later
invoked inside greet . We could also use an anonymous arrow function as the callback:

greet("Bob", () => {
console.log("This is an anonymous callback!");
});
// Output:
// Hello, Bob!
// This is an anonymous callback!

1
Here, an arrow function is passed directly as the callback argument and executed inside greet .

Common Callback Use Cases


Callbacks are everywhere in JavaScript APIs. They are used for event handling, timers, array iteration,
and more. Below are some common patterns:

• Event Listeners: Web APIs like addEventListener use callbacks to respond to user
interactions. For example:

const button = document.getElementById("myButton");


button.addEventListener("click", function(event) {
console.log("Button was clicked!");
});

In this snippet, we register a click handler: when the button is clicked, the callback function is invoked
3 . The addEventListener method “sets up a function that will be called whenever the specified event

is delivered to the target.” 3 .

• Timers ( setTimeout / setInterval ): Timing functions take callbacks to run code after a
delay. For example:

console.log("Start");
setTimeout(() => {
console.log("Async callback after 1 second");
}, 1000);
console.log("End");
// Output:
// Start
// End
// (after ~1 second) Async callback after 1 second

Here, setTimeout schedules the arrow-function callback to run after 1000 ms. setTimeout “sets a
timer which executes a function … once the timer expires” 4 . The output shows that “End” is logged
before the callback runs, demonstrating asynchronous timing.

• Array Iteration: Many array methods take callbacks for processing elements. For example,
forEach calls a callback on each array element 5 :

const fruits = ['Apple', 'Banana', 'Cherry'];


fruits.forEach(function(item, index) {
console.log(`${index}: ${item}`);
});
// Output:
// 0: Apple
// 1: Banana
// 2: Cherry

2
The forEach method “executes a provided function once for each element in an array” 5 . Other
methods like map , filter , and reduce similarly use callbacks to transform or filter arrays.

• Higher-Order Functions: Functions that take callbacks (or return functions) are called higher-
order functions. Any time you pass a function as an argument (like the examples above), you’re
using a higher-order function 6 . This ability makes JavaScript flexible and modular.

Callbacks in Asynchronous Programming


JavaScript runs in a single thread, so long-running tasks (like I/O or network requests) use asynchronous
callbacks to avoid blocking the main thread 7 . When you call an asynchronous API (e.g.,
setTimeout , fetch , file I/O, or DOM events), JavaScript registers your callback and continues
running other code. Once the asynchronous operation completes, the event loop ensures your callback
is called (often by moving it from a queue onto the call stack) 7 8 .

For instance, consider this code:

console.log("First");
setTimeout(() => {
console.log("Third (inside setTimeout)");
}, 500);
console.log("Second");

The output will be:

First
Second
Third (inside setTimeout)

Because setTimeout is asynchronous, the code does not wait 500ms before logging “Second”.
Instead, “Second” appears immediately, and the callback runs later (after about 500ms). This behavior is
central to non-blocking programming: the event loop handles the timer’s expiration and invokes the
callback when ready.

3
Figure: Node.js event loop with multiple callback queues (timers, I/O, check, close) and microtask queues
(nextTick, Promises). The event loop coordinates when callbacks run.

The event loop (illustrated above) continuously checks the call stack and task queues. When the stack is
empty, it takes the next callback from a queue and pushes it to the stack for execution 8 9 . This
mechanism allows JavaScript to handle multiple async tasks (timers, network requests, etc.) without
freezing the UI 7 10 .

Callback Hell (Pyramid of Doom)


Using callbacks for deeply dependent async tasks can lead to “callback hell” (also known as the “pyramid
of doom”). This happens when callbacks are nested inside callbacks, creating hard-to-read code. For
example:

doTaskA(arg, function(resultA) {
doTaskB(resultA, function(resultB) {
doTaskC(resultB, function(resultC) {
console.log("Final result:", resultC);
// ... and so on ...
});
});
});

Each step depends on the previous one, so we nest callbacks. As more steps are added, the code forms
a growing pyramid. According to authors, when “we nest multiple callbacks within a function, the shape
of the resulting code structure resembles a pyramid… it makes the code very difficult to understand and
maintain” 11 . This anti-pattern complicates both control flow and error handling. It was a major pain
point in early JavaScript development 12 11 .

Problems with Callback Hell:


- Readability suffers as indentation grows.
- Errors at any step must be handled in each callback, leading to repetitive code.
- It’s easy to forget a return , misplaced braces, or try/catch, causing bugs.

4
In short, callback hell is “an ineffective way of writing code asynchronously” 13 , and developers often try
to avoid it through other patterns.

Promises: Evolution from Callbacks


Promises were introduced in ES2015 (ES6) to address callback hell and make async code cleaner. A
Promise represents the eventual result of an asynchronous operation. Instead of passing callbacks, you
work with promise objects. A promise can be in one of three states: pending, fulfilled, or rejected 14 .
Once settled, it either carries a successful value or an error.

By chaining .then() handlers, you avoid deep nesting. For example, converting a nested call to
promises:

doTaskA(arg)
.then(resultA => doTaskB(resultA))
.then(resultB => doTaskC(resultB))
.then(resultC => {
console.log("Final result:", resultC);
})
.catch(err => {
console.error("Error occurred:", err);
});

Here each step returns a promise, and the next .then() runs after the previous promise resolves.
There is no pyramid of indentation. As one tutorial notes, chaining .then() calls “allows the
consumption of promises to appear more synchronous than callbacks, as they do not need to be
nested” 15 . The result is more linear and readable code.

// Example: promise with setTimeout


function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
console.log("Start delay");
delay(1000).then(() => {
console.log("One second has passed");
});
// Output:
// Start delay
// (after ~1s) One second has passed

This promise-based delay replaces a callback-style setTimeout . Promises also have an explicit
mechanism for errors: .catch() handles rejections, whereas callbacks require manual error checks.

5
Figure: Flowchart of Promise states and chaining. A Promise starts pending, then becomes fulfilled (with
.then ) or rejected (with .catch ). Chaining .then() returns new promises for sequential async steps.

As illustrated above, a pending promise moves to fulfilled or rejected, and the appropriate handler runs
16 . The MDN guide notes that promises allow “asynchronous methods to return values like synchronous

methods” by returning a placeholder that will get the value in the future 14 . This design simplifies many
patterns: ES2015 APIs like fetch() use promises, and developers can sequence operations cleanly.

Async/Await: Syntactic Sugar for Promises


async / await (introduced in ES2017) further simplify promise-based code by letting you write
asynchronous code in a synchronous-looking style. An async function automatically returns a
promise, and inside it you can use await to pause execution until another promise resolves 17 18 .
This eliminates explicit .then() chains and makes error handling easier with try/catch .

For example, using fetch with then :

fetch(url)
.then(response => response.json())
.then(data => {
console.log("Data received:", data);
})
.catch(error => {
console.error("Error:", error);
});

With async/await, the same logic becomes:

async function fetchData() {


try {
console.log("Fetching data...");
const response = await fetch(url);
const data = await response.json();
console.log("Data received:", data);
} catch (err) {
console.error("Error:", err);
}

6
}
fetchData();

This async function pauses at each await until the promise resolves or rejects 17 . If an error is
thrown (or a promise rejects), it can be caught in the surrounding try/catch . In effect, async/await is
“enabling asynchronous, promise-based behavior to be written in a cleaner style and avoiding the need to
explicitly configure promise chains” 17 . Under the hood, async / await still use promises, but they
greatly improve readability.

Advanced Concepts

Higher-Order Functions

A higher-order function is one that takes another function as an argument or returns a function 6 .
Callbacks enable higher-order patterns. For example:

function repeat(action) {
action();
action();
}
function sayHi() {
console.log("Hi!");
}
repeat(sayHi);
// Output:
// Hi!
// Hi!

Here, repeat is a higher-order function because it accepts action (a function) as a parameter and
calls it. Higher-order functions make code more reusable and abstract. Many built-in functions
( forEach , map , etc.) and libraries rely on this idea. By passing different callbacks, you can customize
behavior.

Function Composition

Function composition involves combining simple functions to build more complex ones. You can write
utilities that return a new function which calls callbacks in sequence. For example:

function compose(f, g) {
return function(x) {
return f(g(x));
};
}
const greet = name => `Hello, ${name}`;
const exclaim = sentence => sentence + "!";
const excitedGreet = compose(exclaim, greet);
console.log(excitedGreet("Alice")); // Hello, Alice!

7
In compose , we return a new function that, when called, runs g on the input and then runs f on
the result. Here, compose(exclaim, greet) returns a function that first greets the name, then adds
an exclamation mark. This is a simple example of function composition using callbacks. Such patterns
are common in functional programming.

Error Handling in Callback Code


In callback-based APIs (especially in Node.js), a common convention is the error-first callback. This
means callbacks expect an error object as their first argument and the successful result as the second.
For example:

const fs = require('fs');
fs.readFile('file.txt', (err, data) => {
if (err) {
console.error("Read error:", err);
return;
}
console.log("File data:", data.toString());
});

In this Node.js example, fs.readFile invokes the callback with an err if something went wrong,
or with the data if successful. GeeksforGeeks explains: “The first argument is reserved for the error
object by the function. This error object is returned by the first argument whenever any error occurs” 19 . We
check if (err) , handle it, and then proceed.

This pattern forces you to explicitly manage errors in each callback. If you forget to check err , bugs
can sneak in. Unlike exceptions in synchronous code, you can’t use try/catch around an
asynchronous callback to catch errors thrown inside it (the callback runs later, outside the original call
stack). Instead, you must either check the error argument or use the promise equivalent (which can use
.catch() or try/catch with await ).

In more complex callback chains, propagating errors becomes cumbersome, which is another reason
promises and async/await were introduced. With promises, a thrown error or rejected promise in any
step skips to the nearest .catch() . With async/await, you can wrap multiple awaits in a single try/
catch . Nonetheless, understanding the callback pattern is important, since many legacy APIs and
Node.js functions still use it.

Summary
A callback in JavaScript is simply a function passed into another function to be executed later. They are
the backbone of JavaScript’s event-driven and asynchronous nature 1 2 . We covered how to write
callbacks with examples: event handlers ( addEventListener ), timers ( setTimeout ), and array
methods ( forEach ) all use callbacks 3 4 5 . In asynchronous code, callbacks let JavaScript
continue processing while waiting for tasks to complete. However, complex nested callbacks lead to
“callback hell” (the pyramid of doom) 11 12 . To address this, JavaScript evolved: Promises offer
chained handlers, improving readability and error handling 15 14 . Async/await builds on promises to
make async code look synchronous 17 18 . We also saw advanced topics: higher-order functions,
function composition, and the Node.js error-first callback convention 6 19 .

8
By moving from callbacks to promises and async/await, JavaScript developers can write clearer and
more maintainable asynchronous code. Yet callbacks remain a key concept: they enable event-driven
programming and functional patterns that are idiomatic in JavaScript. Understanding callbacks
thoroughly—from basic syntax to advanced usage—empowers you to work with any JavaScript API,
legacy or modern, and to write robust async code.

1 Callback function - MDN Web Docs Glossary: Definitions of Web-related terms | MDN
https://developer.mozilla.org/en-US/docs/Glossary/Callback_function

2 JavaScript Callbacks
https://www.w3schools.com/js/js_callback.asp

3 EventTarget: addEventListener() method - Web APIs | MDN


https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener

4 Window: setTimeout() method - Web APIs | MDN


https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout

5 Array.prototype.forEach() - JavaScript | MDN


https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

6 JavaScript Higher Order Functions | GeeksforGeeks


https://www.geeksforgeeks.org/javascript-higher-order-functions/

7 12 15 Understanding the Event Loop, Callbacks, Promises, and Async/Await in JavaScript |


DigitalOcean
https://www.digitalocean.com/community/tutorials/understanding-the-event-loop-callbacks-promises-and-async-await-in-
javascript

8 9 10 What is An Event Loop in JavaScript? | GeeksforGeeks


https://www.geeksforgeeks.org/what-is-an-event-loop-in-javascript/

11 13 What is Callback Hell in JavaScript? - Scaler Topics


https://www.scaler.com/topics/callback-hell-in-javascript/

14 16 Promise - JavaScript | MDN


https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

17 18 async function - JavaScript | MDN


https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

19 What is an error-first callback in Node.js ? | GeeksforGeeks


https://www.geeksforgeeks.org/what-is-an-error-first-callback-in-node-js/

You might also like