Friday, April 10, 2020
Home Node.js What is Callback Hell in Node.js and How to avoid it

What is Callback Hell in Node.js and How to avoid it

Author

Date

Category

In this article, we are going to read about Callback Hell. As many of you probably know, JavaScript is a single-threaded event loop that processes queued events. If you were to execute a long-running task within a single thread then the process would block, causing other events to have to wait to be processed, which causes your website or applications to freeze for some time.

To solve this blocking problem, JavaScript heavily relies on callbacks, which are functions that run after another function has finished executing, thus allowing the code execution to proceed past the long-running task. If you want to know more about callbacks, you can read more about it here. An example of a simple callback function would be:

// First Function with a callback
function One(callback) {
    setTimeout(function () {
        console.log("This is function one");
        callback();
    }, 1000);
}

// Second Function 
function Two() {
    console.log("This is function two");
}

One(function(){
    Two();
});

Now what is the problem with these callbacks?

The Problem: Callback Hell

Well, callback is quite amazing concept while dealing with Asynchronous programming. But imagine a scenario, where you have to write callbacks after callbacks. It will decrease the readability of the code increasing the chance of confusion. Just see the example:

function One(a, callback) {
    callback(a);
}

function Two(b, callback) {
    callback(b);
}

One("a", function (b) {
    Two(b, function (c) {
        Two(c, function (d) {
            Two(d, function (e) {
                // And so on
            });
        });
    });
});

You can see the pyramid style structure in above code. I know it is easy to understand it right now. But, think about a case where you have to add some if statements or for loops. It can really go out of hand. And most of the time the novice programmers become the victim of this kind of situation. We call it, Callback Hell.

How to Avoid Callback Hell

Changing the code Structure

Many programmers face callback hell, mostly due to a bad coding structure. They don’t really think about their code structure ahead of time and don’t realize how bad their code has gotten until after its too late. When you start with your program, you should think of different ways to avoid Callback hell, right from the beginning. Here are few tips you can try for that.

Modularize Codes

In just about every programming language, one of the best ways to reduce complexity is to modularize. JavaScript is no different in this case. Whenever you’re writing code, take some time to figure out if there has been a common pattern you are frequently using and then try to make module for that. By doing this, not only you can minimize the code complexity, but also you can increase the code reusability.

Do Appropriate Nomenclature

When reading code (especially messy ones), it is easy to lose track of the logic flow, or even syntax, when small spaces are congested with so many nested callbacks. One way to help combat this is to name your functions, so all you’ll have to do is glance at the name and you’ll a better idea as to what it does. It also gives your eyes a syntax reference point.

Handle every single error

There are different types of errors: syntax errors due to syntactical miss configuration (usually caught when you try to first run the program), runtime errors caused by the programmer (the code ran but had a bug that caused something to mess up), platform errors caused by things like invalid file permissions, hard drive failure, no network connection etc. This section is only meant to address this last class of errors.

The first three rules are primarily about making your code readable, but this one is about making your code stable. When dealing with callbacks you are by definition dealing with tasks that go off and do something in the background, and then complete successfully or abort due to failure. Any experienced developer will tell you that you can never know when these errors happen, so you have to plan on them always happening.

Let us take an example:

var fs = require('fs')

fs.readFile('File/Does/not/exist', handleFile)

function handleFile(error, file) {
    if (error) return console.error('There was an error', error)
    // otherwise, continue on and use `file` in your code
}

Here we can use the first argument error for error handling and second argument actually returns the desired results. Without it, the code will execute perfectly fine. But you won’t be able to handle those errors.

Using Async

Async is a utility module which provides straight-forward, powerful functions for working with asynchronous JavaScript and to deal with Callback Hell. Although originally designed for use with Node.js and installable via npm install --save async, it can also be used directly in the browser.

// for use with Node-style callbacks...
var async = require("async");
 
var obj = {dev: "/dev.json", test: "/test.json", prod: "/prod.json"};
var configs = {};
 
async.forEachOf(obj, (value, key, callback) => {
    fs.readFile(__dirname + value, "utf8", (err, data) => {
        if (err) return callback(err);
        try {
            configs[key] = JSON.parse(data);
        } catch (e) {
            return callback(e);
        }
        callback();
    });
}, err => {
    if (err) console.error(err.message);
    // configs is now a map of JSON data
    doSomethingWith(configs);
});

Promise

Another solution to the Callback hell problem is using of Promise. Promises in JavaScript are a way to handle async calls. Before Promises were introduced in JavaScript ES6, async calls in JavaScript were handled using callback functions. Promises provide a cleaner, more elegant syntax and methodology to handle async calls. Not only did it reduce the number of lines of code drastically, but it made the logical flow of the code much easier to follow. How ever, we will talk about Promise in another article as it is not a small concept to cover it up here.

var express = require('express');
var app = express();

function One() {
  return new Promise(function (resolve, reject) {
    let thisIsOne = true; 
    if(thisIsOne) {
      resolve("You are in function One().");
    } else {
      reject("You are not in function One().")
    }
    
  })
}

app.get('/', function(req, res, next) {
  var promise = One();
  promise.then(function onResolved(data) {
    res.send(data);
  }, function nReject(data) {
    res.send(data);
  });
});

module.exports = app;

The problem with Promise is, it Take the time to learn and understand Promises, it’ll be worth your time. However, Promises are definitely not the solution to all your problems in asynchronous programming, so don’t assume by using them you will have a fast, clean, bug-free app. The key is knowing when they’ll be useful to you.

A few Promise libraries you should check out are QBluebird, or the built-in Promises if you’re using ES6. We have a nice article on How to use BlueBird Promise Library in Node.js, if you want to know more about it, have a look.

Conclusion

Getting rid of Callback Hell is not quite easy if you do not follow proper coding principles and conduct self audits to your own codes. However, it can be avoided by using above methods. I might be missing some points over here, which will get added eventually. If you want me to add something, comment down bellow.

Happy Coding!

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Subscribe to our newsletter

Snehasish Nayak
I am Snehasish Nayak, a Senior Programmer at NIC, Bhubaneswar and a Google Product Expert.

Recent posts

Recent comments