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)
}

When we call the function that is supposed to be asynchronous, processing continues without waiting for the result. But if we want to display the result, which is only available at the end of the processing, how to do?

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

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

And we call the function with a callback:

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

This is this callback we want to avoid.

2) A named function to avoid spaghetti code

Function given to getFibo as parameter is moved in a named function, and replaced by a reference to the name of that function.

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

getFibo(display)

The function is thus reusable, and the source code is even more because it is clearer and more readable.

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

3) Generators?

If instead of using a named function, 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 also 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...

4) 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 ...

5) 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...

6) Chaining promises

The following code replaces all the previous code:

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.

All these examples are combined in a single JavaScript file that you can run from the command line with io.js.

Download the demo.

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 Xul.fr