Working with Async JavaScript

If you have been programming with most modern programming languages, the first thing you realize when learning how to write JavaScript is that:

  • It isn't procedural
  • It is asynchronous
  • It requires a rewiring of your brain to develop software

Anonymous vs Named Functions

If you haven't worked with JavaScript much, note that functions are objects and functions can either be anonymous, or they can have a name associated with them. In the below example, we assign an anonymous function function() to a variable var print_hello_world and then the function executes when we call it via the variable:

Anonymous Function in JavaScript:

var print_hello_world = function(){  
    console.log('Hello World');
};

// call the anonymous function via the var
print_hello_world();  

Console output:

Hello World  

Run the above example on JSFiddle.

In the next example, we name the function function hello_world(). Instead of assigning it to a variable, we can just call it directly since we know the function's name:

Named Function in JavaScript:

function hello_world(){  
    console.log('Hello World');
};

hello_world();  

Console output:

Hello World  

Run the above example on JSFiddle.

Asynchronous JavaScript in Action

In the following example, we can clearly see that even though the function one() gets called first, JavaScript wastes no time waiting around for the 2 second setTimeout() function to complete. It keeps on running and the console's output of the function two(), 'second function', is displayed before 'first function'.

JavaScript:

function one(){  
    setTimeout(function(){
        console.log('first function');
    }, 2000);
}

function two(){  
    console.log('second function');
}

// call the functions...
one();  
two();  

Console output:

second function  
first function  

Run the above example on JSFiddle.

So how do we ensure the order of code execution? Well the first hint was inside function one() where we called a function within setTimeout...

JavaScript Callbacks

Since functions in JavaScript are objects, we can pass them as an argument. In the following example, we define the order of code execution by using a callback. Now, the console will always output 'first function' before 'second function'.

JavaScript:

function one(two){  
    setTimeout(function(){
        console.log('first function');
        two();
    }, 2000);
}

function two(){  
    console.log('second function');
}

// call the functions, but pass the function two as a callback...
one(two);  

Console output:

first function  
second function  

Run the above example on JSFiddle.

This is extremely important when we need to do I/O operations, such as accessing data from a database or even from a remote server. Since it usually takes a long time to do these things, we:

  • Don't want to lock up the system waiting around while this I/O data is being accessed
  • We want to make sure that I/O data has been retrieved before we attempt to act on it

An example to emphasize the second point: Do we want to attempt to display data retrieved from a database on a screen before the retrieval process has completed? Of course not. Hence the use of callback to manage a situation like this.

Callback Hell

So what happens if we need to have multiple levels of nested callbacks to ensure that 10 things need to happen before we do the 11th thing? We end up in a state of callback hell where the code is nearly impossible to read and trace. The example below is an extremely clean and generous example of this phenomenon. Imagine multiple lines of error handling, HTTP Requests, file handling, image manipulation, data validation and more all rolled into a stew like this. No one enjoys a hostile work environment.

JavaScript:

setTimeout(function(){  
    console.log("Inside callback 1");
    setTimeout(function(){
        console.log("Inside callback 2");
        setTimeout(function(){
            console.log("Inside callback 3");
            setTimeout(function(){
                console.log("Inside callback 4");
            }, 1000);
        }, 1000);
    }, 1000);
}, 1000);

Console output:

Inside callback 1  
Inside callback 2  
Inside callback 3  
Inside callback 4  

Run the above example on JSFiddle.

Solution 1: Naming our functions

One strategy to better manage the callback hell is to use named functions instead of anonymous functions. While this doesn't completely solve the issue, we can at least identify what each function is supposed to be doing and also quickly identify functions for things, such as tests or errors.

JavaScript:

setTimeout(function one(){  
    console.log("Inside callback 1");
    setTimeout(function two(){
        console.log("Inside callback 2");
        setTimeout(function three(){
            console.fog("Inside callback 3"); //intentional error
            setTimeout(function four(){
                console.log("Inside callback 4");
            }, 1000);
        }, 1000);
    }, 1000);
}, 1000);

Console output:

Inside callback 1  
Inside callback 2  
Uncaught TypeError: console.fog is not a function: three @ (index):29  

Run the above example on JSFiddle.

Solution 2: Breaking up nested code levels

Another strategy to use is to break code into smaller pieces. While some might argue that you increase the size of the JavaScript file by doing this, it helps make code significantly more manageable for developers.

JavaScript:

function one(cb){  
    console.log("Inside callback 1");
    setTimeout(cb, 1000);
}
function two(cb){  
    console.log("Inside callback 2");
    setTimeout(cb, 1000);
}
function three(cb){  
    console.log("Inside callback 3");
    setTimeout(cb, 1000);
}
function four(){  
    console.log("Inside callback 4");
}

one(function(){  
    two(function(){
        three(four);
    });
});

Console output:

Inside callback 1  
Inside callback 2  
Inside callback 3  
Inside callback 4  

Run the above example on JSFiddle.

Solution 3: Using Promises

While the two previous are helpful, my favorite way of managing asynchronous code is by using Promises. Promises allow you to not only handle asynchronous code, but we also get additional built-in functionality that adds things like "long stack traces" and error handling that resembles the synchronous try/catch.

Promise libraries are available for ECMAScript 5, and a Promise implementation is native in ECMAScript 6, also known as ECMAScript 2015. Available libraries include:

Promises in JavaScript

Using the Q library, here are two simple examples that illustrate chaining function calls together in sequence using then and how the output changes depending on if we return a promise or not:

JavaScript - No Promises Returned:

function one(){  
    console.log("Inside function 1");
}
function two(){  
    setTimeout(function(){
            console.log("Inside function 2");
        }, 3000);
}
function three(){  
    console.log("Inside function 3");
}
function four(){  
    console.log("Inside function 4");
}

// call functions in order
Q.fcall(one)  
.then(two)
.then(three)
.then(four);

Console output:

Inside function 1  
Inside function 3  
Inside function 4  
Inside function 2  

Run the above example on JSFiddle.

In the above example, even though we now have the ability to easily run the functions in sequence, we aren't returning any promises. Each function is called in order, but it never waits for the function to complete in function two() before running the next function. Because of the delay, function two() is the last function to complete.

Now lets see the control flow when we create a Promise and return it via resolve() in function two() in the example below:

JavaScript - Promise Returned:

function one(){  
    console.log("Inside function 1");
}
function two(){  
    return Q.Promise(function(resolve, reject, notify) {
        setTimeout(function(){
            resolve(console.log("Inside function 2"));
        }, 3000);
    });
}
function three(){  
    console.log("Inside function 3");
}
function four(){  
    console.log("Inside function 4");
}

// call functions in order
Q.fcall(one)  
.then(two)
.then(three)
.then(four);

Console output:

Inside function 1  
Inside function 2  
Inside function 3  
Inside function 4  

Run the above example on JSFiddle.

To explain what is going on here, each function is also being called in sequence. In function two(), there is a 3 second time delay to exhibit how function three() doesn't get called until the promise is returned as successful by function two() via resolve(). In functions one(), three() and four(), we don't return anything or have any errors so the processing of the function calls proceeds without any issues like the previous example.

These examples don't even come close to exhibiting the power that promises can give you, but it should begin to give you a glimpse of what is possible using promises in developing async code in JavaScript.