Advertisement
JavaScript & AJAX

Event-Based Programming: What Async Has Over Sync

by

One of JavaScript's strengths is how it handles asynchronous (async for short) code. Rather than blocking the thread, async code gets pushed to an event queue that fires after all other code executes. It can, however, be difficult for beginners to follow async code. I'll help clear up any confusion you might have in this article.


Understanding Async Code

JavaScript's most basic async functions are setTimeout and setInterval. The setTimeout function executes a given function after a certain amount of time passes. It accepts a callback function as the first argument and a time (in milliseconds) as the second argument. Here's an example of its usage:

console.log( "a" );
setTimeout(function() {
    console.log( "c" )
}, 500 );
setTimeout(function() {
    console.log( "d" )
}, 500 );
setTimeout(function() {
    console.log( "e" )
}, 500 );
console.log( "b" );

As expected, the console outputs "a", "b", and then 500 ms(ish) later, we see "c", "d", and "e". I use "ish" because setTimeout is actually unpredictable. In fact, even the HTML5 spec talks about this issue:

"This API does not guarantee that timers will run exactly on schedule. Delays due to CPU load, other tasks, etc, are to be expected."

Interestingly, a timeout will not execute until all of the remaining code in a block has executed. So if a timeout is set, and then some long running function executes, the timeout will not even start until that long running function has finished. In actuality, async functions like setTimeout and setInterval are pushed onto an queue known as the Event Loop.

The Event Loop is a queue of callback functions. When an async function executes, the callback function is pushed into the queue. The JavaScript engine doesn't start processing the event loop until the code after an async function has executed. This means that JavaScript code is not multi-threaded even though it appears to be so. The event loop is a first-in-first-out (FIFO) queue, meaning that callbacks execute in the order they were added onto the queue. JavaScript was chosen for the language of node because of how easy it is to write this kind of code.


Ajax

Asynchronous JavaScript and XML (Ajax) forever changed the landscape of JavaScript. All of the sudden, a browser could update a web page without having to reload it. The code for implementing Ajax in different browsers can be long and tedious to write; however, thanks to jQuery (and other libraries), Ajax became an extremely easy and elegant solution to facilitate client-server communication.

Asynchronously retrieving data with jQuery's $.ajax is an easy cross-browser process, but it's not immediately evident as to what exactly happens behind the scenes. For example:

var data;       
$.ajax({
    url: "some/url/1",
    success: function( data ) {
        // But, this will!
        console.log( data );
    }
})
// Oops, this won't work...
console.log( data );

It's common, but incorrect, to assume the data is available immediately after calling $.ajax, but what actually happens is this:

xmlhttp.open( "GET", "some/ur/1", true );
xmlhttp.onreadystatechange = function( data ) {
    if ( xmlhttp.readyState === 4 ) {
        console.log( data );
    }
};
xmlhttp.send( null );

The underlying XmlHttpRequest (XHR) object sends the request, and the callback function is set to handle the XHR's readystatechange event. Then the XHR's send method executes. As the XHR performs its work, an internal readystatechange event fires every time the readyState property changes, and it's only when the XHR finishes receiving a response from the remote host that the callback function executes.


Working With Async Code

Async programming lends itself to what's commonly referred to as "callback hell". Because virtually all async functions in JavaScript use callbacks, performing multiple sequential async functions result in many nested callbacks--resulting in hard to read code.

Many of the functions within node.js are async. So, code like the following is quite common.

var fs = require( "fs" );

fs.exists( "index.js", function() {
    fs.readFile( "index.js", "utf8", function( err, contents ) {
        contents = someFunction( contents ); // do something with contents
        fs.writeFile( "index.js", "utf8", function() {
            console.log( "whew! Done finally..." );
        });
    }); 
});
console.log( "executing..." );

It's also common to see client-side code, like the following:

GMaps.geocode({
    address: fromAddress,
    callback: function( results, status ) {
        if ( status == "OK" ) {
            fromLatLng = results[0].geometry.location;
            GMaps.geocode({
                address: toAddress,
                callback: function( results, status ) {
                    if ( status == "OK" ) {
                        toLatLng = results[0].geometry.location;
                        map.getRoutes({
                            origin: [ fromLatLng.lat(), fromLatLng.lng() ],
                            destination: [ toLatLng.lat(), toLatLng.lng() ],
                            travelMode: "driving",
                            unitSystem: "imperial",
                            callback: function( e ){
                                console.log( "ANNNND FINALLY here's the directions..." );
                                // do something with e
                            }
                        });
                    }
                }
            });
        }
    }
});

Nested callbacks can get really nasty, but there are several solutions to this style of coding.

The problem isn’t with the language itself; it’s with the way programmers use the language -- Async Javascript.

Named Functions

An easy solution that cleans nested callbacks is simply avoiding nesting more than two levels. Instead of passing anonymous functions to the callback arguments, pass a named function:

var fromLatLng, toLatLng;

var routeDone = function( e ){
    console.log( "ANNNND FINALLY here's the directions..." );
    // do something with e
};

var toAddressDone = function( results, status ) {
    if ( status == "OK" ) {
        toLatLng = results[0].geometry.location;
        map.getRoutes({
            origin: [ fromLatLng.lat(), fromLatLng.lng() ],
            destination: [ toLatLng.lat(), toLatLng.lng() ],
            travelMode: "driving",
            unitSystem: "imperial",
            callback: routeDone
        });
    }
};

var fromAddressDone = function( results, status ) {
    if ( status == "OK" ) {
        fromLatLng = results[0].geometry.location;
        GMaps.geocode({
            address: toAddress,
            callback: toAddressDone
        });
    }
};

GMaps.geocode({
    address: fromAddress,
    callback: fromAddressDone
});

Additionally, the async.js library can help handle multiple Ajax requests/responses. For example:

async.parallel([
    function( done ) {
        GMaps.geocode({
            address: toAddress,
            callback: function( result ) {
                done( null, result );
            }
        });
    },
    function( done ) {
        GMaps.geocode({
            address: fromAddress,
            callback: function( result ) {
                done( null, result );
            }
        });
    }
], function( errors, results ) {
    getRoute( results[0], results[1] );
});

This code executes the two asynchronous functions, and each function accepts a "done" callback that executes after the async function finishing running. When both "done" callbacks finish, the parallel function's callback executes and handles any errors or results from the two async functions.

Promises

From the CommonJS/A:

A promise represents the eventual value returned from the single completion of an operation.

There are many libraries that incorporate the promise pattern, and jQuery users already have a nice promise API available to them. jQuery introduced the Deferred object in version 1.5, and using the jQuery.Deferred constructor results in a function that returns a promise. A promise-returning function performs some sort of async operation and resolves the deferred upon completion.

var geocode = function( address ) {
    var dfd = new $.Deferred();
    GMaps.geocode({
        address: address,
        callback: function( response, status ) {
            return dfd.resolve( response );
        }
    });
    return dfd.promise();
};

var getRoute = function( fromLatLng, toLatLng ) {
    var dfd = new $.Deferred();
    map.getRoutes({
        origin: [ fromLatLng.lat(), fromLatLng.lng() ],
        destination: [ toLatLng.lat(), toLatLng.lng() ],
        travelMode: "driving",
        unitSystem: "imperial",
        callback: function( e ) {
            return dfd.resolve( e );
        }
    });
    return dfd.promise();
};

var doSomethingCoolWithDirections = function( route ) {
    // do something with route
};

$.when( geocode( fromAddress ), geocode( toAddress ) ).
    then(function( fromLatLng, toLatLng ) {
        getRoute( fromLatLng, toLatLng ).then( doSomethingCoolWithDirections );
    });

This allows you to execute two asyncrhonous functions, wait for their results, and then execute another function with the results of the first two calls.

A promise represents the eventual value returned from the single completion of an operation.

In this code, the geocode method executes twice and returns a promise. The async functions then execute and call resolve in their callbacks. Then, once both have called resolve, the then executes, returning the results of the first two calls to geocode. The results are then passed to getRoute, which also returns a promise. Finally, when the promise from getRoute is resolved, the doSomethingCoolWithDirections callback executes.

Events

Events are another solution to communicate when async callbacks finish executing. An object can become an emitter and publish events that other objects can listen for. This type of eventing is called the observer pattern. The backbone.js library has this type of functionallity built in with Backbone.Events.

var SomeModel = Backbone.Model.extend({
   url: "/someurl" 
});

var SomeView = Backbone.View.extend({
    initialize: function() {
        this.model.on( "reset", this.render, this );

        this.model.fetch();
    },
    render: function( data ) {
        // do something with data
    }
});

var view = new SomeView({
    model: new SomeModel()
});

There are other mixin examples and libraries for emitting events, such as jQuery Event Emitter, EventEmitter, monologue.js, and node.js has a built-in EventEmitter module.

The Event Loop is a queue of callback functions.

A similar method of publishing messages uses the mediator pattern, used in the postal.js library. In the mediator pattern, a middleman for all objects listens to and publishes events. In this approach, one object does not have a direct reference to another, thereby de-coupling the objects from each other.

Never return a promise accross a public API. This couples the API consumers to using promises and makes refactoring difficult. However, a combination of promises for internal purposes and eventing for external APIs can lead to a nicely de-coupled and testable app.

In the previous example, the doSomethingCoolWithDirections callback function executes when the two previous geocode functions have completed. The doSomethingCoolWithDirections can then take the response it recieved from getRoute and publish the reponse as a message.

var doSomethingCoolWithDirections = function( route ) {
    postal.channel( "ui" ).publish( "directions.done",  {
        route: route
    });
};

This allows other areas of the application to respond to the asynchronous callback without needing a direct reference to the request-making object. It's possible that multiple areas of a page need to update when directions have been retrieved. In a typical jQuery Ajax setup, the success callback would need to be adjusted when recieving a change of directions. This can become difficult to maintain, but by using messaging, updating multiple parts of the UI is much easier to work with.

var UI = function() {
    this.channel = postal.channel( "ui" );
    this.channel.subscribe( "directions.done", this.updateDirections ).withContext( this );
};

UI.prototype.updateDirections = function( data ) {
    // The route is available on data.route, now just update the UI
};    

app.ui = new UI();

Some other mediator pattern-based messaging libraries are amplify, PubSubJS, and radio.js.


Conclusion

JavaScript makes writing async code very easy. Using promises, eventing, or named functions eliminates the nasty "callback hell". For more information on async JavaScript, checkout Async JavaScript: Build More Responsive Apps with Less Code. Many of the examples from the post reside in a Github repository called NetTutsAsyncJS. Clone away!

Related Posts
  • Code
    JavaScript & AJAX
    Connect 4 With Socket.ioSocket io wide retina preview
    Today we'll see how we can use Node.js and Socket.io to create a multiplayer Connect 4 style game.Read More…
  • Code
    JavaScript & AJAX
    Testing in Node.jsNodejs testing chai retina preview
    A test driven development cycle simplifies the thought process of writing code, makes it easier, and quicker in the long run. But just writing tests is not enough by itself, knowing the kinds of tests to write and how to structure code to conform to this pattern is what it's all about. In this article we will take a look at building a small app in Node.js following a TDD pattern.Read More…
  • Code
    JavaScript & AJAX
    Using Node's Event ModuleNode event module retina preview
    When I first heard about Node.js, I thought it was just a JavaScript implementation for the server. But it's actually much more: it comes with a host of built-in functions that you don't get in the browser. One of those bit of functionality is the Event Module, which has the EventEmitter class. We'll be looking at that in this tutorial.Read More…
  • Code
    JavaScript & AJAX
    Working With IndexedDBIndexeddb retina preview
    One of the more interesting developments in web standards lately is the Indexed Database (IndexedDB for short) specification. For a fun time you can read the spec yourself. In this tutorial I'll be explaining this feature and hopefully giving you some inspiration to use this powerful feature yourself.Read More…
  • Code
    JavaScript & AJAX
    Promise-Based ValidationPromise retina
    The concept of "Promises" has changed the way we write asynchronous JavaScript. Over the past year, many frameworks have incorporated some form of the Promise pattern to make asynchronous code easier to write, read and maintain. For example, jQuery added $.Deferred(), and NodeJS has the Q and jspromise modules that work on both client and server. Client-side MVC frameworks, such as EmberJS and AngularJS, also implement their own versions of Promises.Read More…
  • Code
    JavaScript & AJAX
    Wrangle Async Tasks With JQuery PromisesJquery promises
    Promises are an exciting jQuery feature that make it a breeze to manage async events. They allow you to write clearer, shorter callbacks and keep high-level application logic separate from low-level behaviors. Once you understand Promises, you'll want to use them for everything from AJAX calls to UI flow. That's a promise!Read More…