“The perils of callback hell and how to overcome them”

sidverma
3 min readDec 11, 2022

In JavaScript, a callback is a function that is passed as an argument to another function and is executed after the parent function has completed. Callbacks are commonly used in JavaScript to perform tasks asynchronously, such as making an HTTP request or setting a timer.

However, when you have multiple nested callbacks, it can quickly become difficult to read and maintain your code. This is known as “callback hell” or “the pyramid of doom”.

Here’s an example of callback hell:

function makeRequest(url, callback) {
// make HTTP request
http.get(url, (response) => {
let data = '';
response.on('data', (chunk) => {
data += chunk;
});
response.on('end', () => {
try {
const parsedData = JSON.parse(data);
callback(null, parsedData);
} catch (error) {
callback(error);
}
});
});
}
makeRequest('https://jsonplaceholder.typicode.com/posts', (error, posts) => {
if (error) {
console.error(error);
} else {
makeRequest(`https://jsonplaceholder.typicode.com/comments?postId=${posts[0].id}`, (error, comments) => {
if (error) {
console.error(error);
} else {
makeRequest(`https://jsonplaceholder.typicode.com/users/${comments[0].userId}`, (error, user) => {
if (error) {
console.error(error);
} else {
console.log(user);
}
});
}
});
}
});

In the above code, we have a makeRequest() function that makes an HTTP request and accepts a callback function. The callback function is executed when the HTTP request is complete.

However, this makeRequest() function is called multiple times and nested within each other. This creates a pyramid-like structure, which is difficult to read and maintain. It also becomes more difficult to handle errors and debug your code.

To avoid callback hell, you can use the async/await syntax introduced in ECMAScript 2017. This allows you to write asynchronous code in a synchronous manner, making it easier to read and maintain.

Here’s the same example using async/await:

async function makeRequest(url) {
// make HTTP request
const response = await http.get(url);
const data = await response.on(‘data’);
return JSON.parse(data);
}
async function main() {
try {
const posts = await makeRequest(‘https://jsonplaceholder.typicode.com/posts');
const comments = await makeRequest(`https://jsonplaceholder.typicode.com/comments?postId=${posts[0].id}`);
const user = await makeRequest(`https://jsonplaceholder.typicode.com/users/${comments[0].userId}`);
console.log(user);
} catch (error) {
console.error(error);
}
}
main();

Here’s an example of what callback hell looks like:

function first(callback) {
setTimeout(function() {
console.log(‘first’);
callback();
}, 1000);
}
function second(callback) {
setTimeout(function() {
console.log(‘second’);
callback();
}, 1000);
}
function third(callback) {
setTimeout(function() {
console.log(‘third’);
callback();
}, 1000);
}
first(function() {
second(function() {
third(function() {
// more nested callbacks…
});
});
});

In the above code, each function uses a callback to call the next function in the sequence. This results in a deeply nested structure that is difficult to read and understand. It also makes the code less maintainable and more prone to errors.

To avoid callback hell, it’s important to keep your code clean and organized. One way to do this is to use promises, which allow you to write asynchronous code in a more readable and manageable way. Another option is to use async/await, which allows you to write asynchronous code in a synchronous-looking style.

In summary, callback hell is a common problem in JavaScript that can make your code difficult to read and maintain. To avoid it, you should use promises or async/await to write asynchronous code in a more manageable way.

--

--