Node Js Callback Hell
Overview
Javascript is a synchronous and single-threaded programming language, but some tasks in javascript can run in parallel due to its asynchronous nature. In javascript, through the use of Callback functions, we can perform asynchronous tasks. Callbacks seem easy to implement but can lead to the problem of callback hell, which makes the code unable to read and maintain. Although the callback hell in Node.js can be easily avoided by many ways; a few of them are using async/ await and Promises.
Pre-requisites
There are few general prerequisites :
- Node.js must be installed on your development system whether it be windows, macOS, or Linux.
- You must be comfortable constructing and executing functions in JavaScript before learning how to use them asynchronously with help of callbacks.
Asynchronous Programming in Node.js
The earlier websites were mainly static, and the data displayed by those websites was in HTML form which was not very interactive. Presently, the websites have become very user-friendly (using javascript) and include various intensive operations like fetching data from any external API. To handle these types of operations, a developer needs to know Asynchronous programming in Nodejs.
Javascript is a single-threaded, synchronous programming language which means the execution of statements takes place one step at a time. However, if we want to fetch data from any API, it can take any amount of undetermined time, which can depend on Network speed, Server availability, and connection traffic.
If API calls were performed in a synchronous manner, then the browser would not be able to maintain interactive behavior (like taking any kind of input from the user, like scrolling, mouse click, or button press) until that API request is complete. This is known as blocking nature. In contrast, asynchronous programming is a technique that ensures non-blocking code execution.
To prevent blocking nature, the browser has numerous Web APIs that JavaScript can access that are asynchronous in nature. It means the statements can run in parallel with other operations. The Event Loop in Node.js was designed to aid with the interactions between the main application thread and asynchronous components. Hence asynchronous programming in Node.js is very important and essential.
Example: Suppose we want to download an image from the internet, then edit the downloaded image (cropping or something else) and then save the edited image on the hard disk of our system.
The above process can be defined using three simple steps:
- Downloading the image from the internet.
- Editing the image.
- Saving the edited image on the hard disk.
All the steps are dependent on the previous steps; hence we need asynchronous programming in Nodejs. This can be achieved by using callbacks in Node.js.
What is a Callback?
A callback function is a simple javascript function that is passed as an argument to another function and is executed when the other function has completed its execution. In layman's terms, a callback is generally used as a parameter to another function. Callbacks in Node.js are so common that you probably used callbacks yourself without understanding that they are called callbacks.
A simple example of a function that everyone must have used must be,
Simplifying the above example:-
Now we can clearly see that the clicked function is a parameter to the addEventListener function and is a callback. Also, the function that takes another function as an argument is called a higher-order function.
Syntactic Code Example of a Higher-Order Function and a Callback
The callback functions are used a lot in asynchronous Node.js, but it does not mean that callbacks are not used in synchronous programming.
Callbacks in Synchronous Programming
There are many functions that use callbacks synchronously. One of them is when you use some of the array methods available in JavaScript.
Output :
Explanation:
In the above example, we are using the array.map() method, which takes in a callback as an argument.
As the function traverses through the given array, each element of the array will be passed in as a parameter into our callback function. The callback function is passed in the map function, intended to be used later when our code executes.
When our code executes, the array.map() function will use our callback function for every single index in our array. We defined what we want the callback function to do, which in this example is to multiply 10 to every argument(in the above case, the number in our array at the current iteration) we pass into the callback function, which is all happening synchronously.
Callbacks in Asynchronous Programming
An example of an asynchronous use of callbacks is when we use the setTimeout() function. setTimeout takes in two arguments, a callback function and how long to delay in milliseconds before running that callback function.
Example :
Output :
Explanation:
In this above example, we have passed a callback function as the second argument to foodOrder, allowing us to use it in our asynchronous setTimeout function. This callback is used for logging our foodData in the console after 5 seconds.
What is Callback Hell in NodeJs?
Callback functions are a useful way to confirm the delayed execution of a function until another function completes its execution and returns with data. However, we may need to nest callbacks inside callbacks, and this nested nature of callbacks can scale horizontally and become unreadable as well as messy if you have a lot of consecutive asynchronous requests that rely on each other. This nesting of callbacks is known as node js callback hell or the Pyramid of Doom.
Here is an example of nested callbacks:
Output :
In the above code, each new setTimeout() is nested inside a higher order function, creating a pyramid of doom or node js callback hell.
Example of Callback Hell in NodeJs
An example showing how node js callback hell is constructed, let us imagine we’re trying to make a burger. To make a burger, we need to follow these steps:
- Get ingredients (we’re gonna assume it’s veg burger)
- Cook the Aloo patty
- Get burger buns
- Put the cooked patty and vegies between the buns
- Serve the burger
Let’s say we can’t make the veg burger ourselves. We have to instruct a helper on the steps to make the burger. After every instruction, we have to wait for the helper to finish his task, then give him the next instruction. Hence if we want to wait for this program in JavaScript, we need to use callbacks.
- To make the burger, we have to get the ingredients first.
- Now we need to instruct the helper to cook the patty. And after that get the buns.
- After we get the buns, we need to put the patty between the buns.
- Finally, we can serve the burger! But we can’t return burger from makeBurger because it’s asynchronous. We need to accept a callback to serve the burger.
A working and running example of callback hell :
Output :
Explanation:
The function and second request to asynchronousFunc are executed because a parameter are passed. The third request to asynchronousFunc is passed with a null parameter. Hence this request throws an error, which affects all the other functions nested inside this one. Hence the executions stops at this request.
Ways to Avoid NodeJs Callback Hell In
Let us take our makeBurger example again and try to avoid callback hell using the following solutions:
Writing Comments
The makeBurger() callback hell in Nodejs is simple to understand to us. We can read it. It just doesn’t look readable and straightforward.
If someone is reading makeBurger() for the first time, they may think, “What is the need for so many nested callbacks to make a simple function?”.
Now the readability of the program has been increased due to the comments.
Split Functions into Smaller Functions
Let us write the step-by-step imperative code of all the functions under makeBurger().
For getIngridients(), the first callback, we have to go to the fridge and get the ingredients like tomatoes, potatoes, and onions. Suppose there are fridges on the top and bottom floor of the kitchen. We need to go to the bottom fridge.
To cook potatoes, we need to wash and peel the potatoes and then fry those potatoes, and wait for 10-20 minutes.
Similarly, we can write other functions also under the makeBurger() function, but this will lead to a large codebase.
Using Promises
We can use promises instead to improve the syntax of the makeBurger(). Promises can make callback hell in the Node.js program much easier to manage and read. Instead of the nested callbacks code you saw in the original example, you will have the following code:
Further simplifying the above code,
We can observe that the above code is much more easier to read and manage than the original example. Now to convert the callback-based code into promise-based code, we need to create a new promise for each callback. Then we need to resolve the promise if the callback is successfully executed or reject the promise if the callback fails.
In Node.js, each of the other functions that contain a callback can be written using the same syntax.
Syntax:
4. Using Async/ await
With Async/ await functions, we can write makeBurger() as if it is synchronous in nature.
One improvement can be made to the makeBurger() method here. You can likely get two helpers to getBuns and getPotatoes at the exact same time. This means you can await them both with Promise.all().
Take your coding skills to new heights with our Free Node JS course. Join today and learn to create efficient and high-performance backend systems.
Conclusion
- Javascript is a synchronous and single-threaded programming language, but some tasks in javascript can run in parallel due to its asynchronous nature.
- Asynchronous nature can be achieved with the help of callbacks in Node.js.
- Callbacks are used in some synchronous methods of Node.js like array.map().
- Callbacks are basically methods that are passed as the parameters to the other function.
- Functions that take other functions as a parameter are known as Higher Order functions.
- Nesting of callbacks can lead to an unreadable and not easy to manageable codebase commonly known as callback hell in Node.js or pyramid of doom.
- Node js callback hell can be avoided using Promises and async / await.
- Splitting of the functions and by writing comments can also be used to avoid callback hell in Node js.