JavaScript without callback

Alternative and simple ways to get rid of 'callback hell', to clean the code.

1) Using a callback

We define a simple function in example, but wich in production would be complex and asynchronous:

function fibo(n) {
    if (n < 2) return n
    return fibo(n-2) + fibo(n-1)
}

We suppose that this function is asynchronous, so if it is called, the processing continues without waiting for the result. And if we do:

console.log(fibo(20))

this command is executed before the result is computed. How to run it at the end of the computation?

Here's the solution, put the display function in parameter:

function getFibo(fn) {
  var x = fibo(20)
  fn(x)	
}

And we call this function with a callback:

getFibo(function(x) {
  console.log("Fibo(20) = " + x) 
})

This is this callback we want to avoid.

Generators are cited sometimes as a mean to replace callbacks...

2) Generators?

If instead of using a callback, a generator was used, it would give the following code:

function *gen() {
  var fiboResult	
  yield fiboResult = fibo(20)
  yield console.log("Generator: fibo(20)=" + fiboResult)
}

var g = gen();
g.next()  // call to fibo
g.next()  // display

This produces a more readable code but the problem is that yield is not asynchronous, it would not work if fibo was an asynchronous function. Generators can only replace callbacks if they are associated with coroutines frameworks such as Bluebird, Co, Q.

A more appropriate solution to avoid loading an external module...

3) Promise instead of callbacks

Promises operate asynchronously and are ideal to replace callbacks. The equivalent code to the code in 1) but without callback is written as:

function display2(x) {
  console.log("Promise: fibo(20)="  + x)
}

function p(fn) {
  var fiboResult =  fibo(20)
  fn(fiboResult)
}

var promise = new Promise(p)
promise.then(display2)

If you are not familiar with this concept appeared in ECMAScript 6, you may consult this tutorial: Promise in JavaScript.

The promise is built on top of p function that calls fibo, and has a fn parameter. This parameter references display2 the function that displays the final result at the then method.

There is still a function as parameter, but the advantage of promises become really obvious to avoid multiple nested callbacks, and we can give an example ...

4) Nested callbacks

Things get a little complicated when we connects several processes:

  1. A Fibonacci suit is calculated for the number 20.
  2. Then the result must be multiplied by two.
  3. And then the end result must be displayed.

It is assumed that these operations are asynchronous, even if we simplified the demonstration with a code that is not.

function getDouble(dbl) {
  var x = fibo(20)
  dbl(x)		
}

function dblFibo(x, fn) {
  var d = x * 2
  fn(d)
}

getDouble(function(x) {
  dblFibo(x, function(y) {
    console.log("Double callback, fibo(20) * 2 = " + y)
  }) 
})	

The dblFibo function is defined as callback of the getDouble function that calculates the first result.
The display of the end result is defined in callback of the function dblFibo which multiplies the result by two.

The fact is that it gets a little more complicated to read (and would be even more with the code of a real application), and we will see how to simplify all that with promises...

5) Chaining promises

The following code obtains the same result while being more readable...

function p1(fn) {
  var fiboResult = fibo(20) 
  fn(fiboResult)
}

function dblFibo2(x) {
  return x * 2
}

function display3(x) {
  console.log("Chaining promise: fibo(20) * 2 = "  + x)
}

var promise1 = new Promise(p1)

promise1.then(dblFibo2).then(display3)

This promise is built on top of the p1 function which has for parameter the dblFibo function that multiplies the result by two. This second function is invoked by the first then.

The second then invokes the function of display display3 after dblFibo although display3 is not defined in callback: this is the advantage of then who always succeed in time, even over asynchronous processing.

6) async/await

Implemented recently in browsers and in Node.js.

function fibo(n) {
    if (n < 2) return n
    return fibo(n-2) + fibo(n-1)
}

function pFibo(n) {
    var p = new Promise(
        function(resolve) {
            setTimeout(function() { resolve(fibo(n)) }, 1000)    
        }
    )
    return p
}

async function getFibo(n) {
   var f = await pFibo(n)
   console.log("Fibo=" + f)
}

console.log("Waiting...")
getFibo(20)

setTimeout is here for demonstration only, just like the Fibonacci calculation. In practice, the result of an asynchronous process is expected.
The function that contains await must be declared async, and the function whose result is expected must contain a promise. So this is a bit more complicated at this level, but the use of this function is much simpler.

If you call pFibo without await, you will get Fibo = Object [Promise] and not the result because the line after the call will be executed before pFibo is finished executing.


All these examples are included in a zip archive to download, and you can run the scripts from the command line with node.js.

Download the demos.

You will notice when running the script that the result of promises appears after all the others, and not in the order they appear in the JS file. This is normal, because they are really asynchronous and not the other examples.

© July 20, 2015 - Updated in 2017 Xul.fr