Front-End Insights

How to avoid callback hell using promises

ProgrammingTutorial

I really hope that all of you have heard about the “callback hell” and today’s post is not neccessary because everyone knows exactly how to handle it using promises… However, there are probably some survivors who haven’t heard about it so today I will explain what that is and how to deal with it.

Ok, lets rock! First of all, let’s see an example of the anti-pattern we will want to improve later:

function asyncFirst (successCallback, failCallback) {
    if (true) {
    	successCallback();    
    } else {
    	failCallback();
    }
}

function asyncSecond (successCallback, failCallback) {
    if (true) {
    	successCallback();    
    } else {
    	failCallback();
    }
}

function asyncThird (successCallback, failCallback) {
    if (true) {
    	successCallback();    
    } else {
    	failCallback();
    }
}

asyncFirst(function () {
	asyncSecond(function () {
    	asyncThird(function () {
            // when all async actions are done...
        	alert('all done!');
        }, function () {
        	// handle errors here
        });
    }, function () {
    	// handle errors here
    });
}, function () {
	// handle errors here
});

As you can see, we have three “async” functions called asyncFirst, asyncSecond and asyncThird. Each has two parameters: the successCallback function is invoked when everything is fine and the failCallback function is called in case of an error.

In the 25th line you can see the most important part of this example. We call the first async function passing a callback to it which invokes the second async function and so on… Additionally, we have several fail callbacks here to handle eventual errors. Generally speaking, everything looks messy.

Let’s make some improvements!

Ok, so how can we do it better? Well, it’s the time when promises appear on the scene! I hope you are familiar with the concept of deferred objects and promises because it’s not the subject of this post (it deserves a separate post – I wrote about it some time ago in Polish and perhaps I will translate it later).

But let’s get back to the point and see below an example of the same code but with the use of the better approach:

function asyncFirst () {
	var deferred = $.Deferred();

    if (true) {
    	deferred.resolve();  
    } else {
    	deferred.reject();
    }
    
    return deferred.promise();
}

function asyncSecond () {
    var deferred = $.Deferred();
    
    if (true) {
    	deferred.resolve();  
    } else {
    	deferred.reject();
    }
    
    return deferred.promise();
}

function asyncThird () {
    var deferred = $.Deferred();
    
    if (true) {
    	deferred.resolve();  
    } else {
    	deferred.reject();
    }
    
    return deferred.promise();
}

$.when(asyncFirst())
    .then(asyncSecond)
    .then(asyncThird)
    .then(function() {
            alert('all done!');
    })
    .fail(function(){
            // handle errors here
    });

An observant reader might notice the main difference. As you can see, we got rid of callbacks from all async functions. Now we have declared a deferred object instead. In place of a success callback we now have a deferred’s resolve function call. The same is done with fail callbacks and reject functions of deferreds. At the end we return a promise.

All of that allows us to do what is done in line 37… You probably noticed that now we have a chain of async functions calls. It’s possible since all these functions return promises – the second async function waits for a resolve or reject in the first async function, the third waits for the second and so on. Finally, when the last async function’s promise is resolved, then we can do our final action (just alert the message in the example). An additional benefit of using this approach is that we only have one place where we can handle errors.

Some might say “but here are callbacks too”… this is true but they are flattened – thanks to promises, we can avoid nested callbacks and that’s what we wanted to achieve!

Last but not least – what if we can’t tweak these async functions?

Many times we don’t have the luxury to change the way our async functions work inside. But we are not on the losing side! What we can do is to wrap the async function with the one which uses promises:

function asyncPromiseWrapper () {
	var deferred = $.Deferred();
    
    asyncFunction(function () {
    	deferred.resolve();
    }, function () {
    	deferred.reject();
    });
    
    return deferred.promise();
}

In the example above we just call the async function inside the wrapper and handle its success and fail callbacks using functions which resolve the promise. It’s so simple, isn’t it?

Summary

I hope that whenever you encounter nested callbacks you won’t wait and refactor them using the presented approach. It’s just easier to read, maintain and debug, so everyone will benefit from it!

Related Post

I recommend Nozbe

Simply Get Everything Done

Get your tasks and projects done thanks to Nozbe system and apps for the Mac, Windows, Linux, Android, iPad and iPhone.

If you want to know more about Nozbe and its capabilities, please take a look at my article about this great application.


  • him

    Dlaczego teraz jest po angielsku?

    • Bartłomiej Dybowski

      why not? 🙂