How to avoid callback hell

Callback hell is typical problem when we have more then two asynchronous calls. I this article I will try to show worst and best practices.

In all examples I am using startjs library for easy write result in html. Library is available in github – https://github.com/StartCodingPL/startjs. All example are using ES6 syntax so best browser to check it working is newest chrome.

Callback hell example

On first attempt I will show the worst possible ( I think ) solution. For async calls I will be using setTimeouts but in real apps it can be everything and mostly it will be ajax call.

See the Pen Async callback hell #1 by Maciej Sikora (@macsikora) on CodePen.

So, as You can see code is quite not readable, it has many nested async calls, such call is difficult to manage, and imagine if we have few more async calls, then it will be real hell to fix it or change it. In real examples callback function can be more complicated so it would look even worst.

First thing which we can change is to create functions for every call. So check upgraded example.

See the Pen Aync Callback hell #2 by Maciej Sikora (@macsikora) on CodePen.

Above example has much cleaner code, it uses function attributes to send callback function inside setTimeout. This is common approach but has one big disadvantage – callback calls are nested and we have many }) signs, so it is difficult to see which function is ending where.

Promise – the better and cleaner way

Nesting callback is no solution, first solution which I will show is Promise, promise is used widely in many js libraries like Q, Bluebird, Jquery Promises and many more. So what is really this Promise thing? In simplest word it is something which will call next function when current promise will be resolved. If we think about some real life algorithms We can take for example restaurant flow, where the cook gives promise to the waiter for that he will prepare a meal, and this is our promise, waiter don’t do anything before this promise is resolved by the cook and when cook resolve it then waiter starts his part.  In return to technical explanation – big advantage is that resolve can be called not only after one async call, but it can be called after many calls.

I will show promise in ES6, it is builded core functionality in ES6, it not needs any third part libraries.

See the Pen Async calls with Promises by Maciej Sikora (@macsikora) on CodePen.

It is big change for better, I will try to compare two last examples.

asyncName(function(){
      
      asyncLastName(function(){
        
        asyncAge(function(){
          
          showFullName();
          
        });
        
      });
      
    });

has been changed to:

asyncName()
   .then(asyncLastName)
   .then(asyncAge)
   .then(showFullName);

It looks much much better, code is readable, in first look everything is clear. For more information about ES6 promise check the documentation – https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

ES6 generators – new approach to asynchronous code execution

So promise is a solution and any library based on this approach also is a solution for this problem, but ES6 give us maybe better one – ES6 generators.

ES6 generators enable to create synchronous looking code which is really asynchronous. Strange but it really works!

See the Pen Use generator with async calls by Maciej Sikora (@macsikora) on CodePen.

Most important thing here is to use * as set that function is generator, without it yield keyword will give an error. So how it is working – every next() method call is running code in generator from last stop to next yield word, and it stops there, so We can really stop code execution and run it again by g.next. Check that g.next() is called in timeouts, it is resuming generator execution.

Let look on the last async calling in generator version.

yield asyncName();
      
yield asyncLastName();
      
yield asyncAge();
    
yield showFullName();

It looks like synchronous code but every yield is like breakpoint for code execution. Lets think about flow in this example:

  1. g.next – runs asyncName() and stops
  2. in asyncName g.next runs code from last stop to asyncLastName()
  3. in asyncLastName g.next runs code from last stop to asyncAge
  4. ….I think You know the rest

Summary

Conclusion – we have two good solutions – use promises or use generators. I don’t want and I don’t need to choose which one is better, I think it always depends on situation. Good practice will be to choose one of this two and use it widely in project, using it together can be confusing because of big differences between them.