Pellucid

Embracing Async in JavaScript (Part 2)

Written by Andy White on November 4, 2014

Intro

In the previous article (part 1), I briefly discussed some fundamentals about the JavaScript event loop, function call stack, closures, and some basic callback patterns, as they relate to async programming. In this article, I would like to continue discussing a few more async topics in JavaScript.

Before getting into that, I would like to quickly respond to a comment from a Redditor on my previous blog post. The comment is rejecting the idea that an entire application must be structured to run asynchronously. It's a great comment, and one I definitely do agree with. In my previous post, I was not trying to imply that you must structure your entire application around plain callbacks or other low-level language features to handle async APIs in your code, but rather that you will quickly encounter async code in the wild, and you'll need to understand and embrace how it works, in order to succeed in the JavaScript space. How you embrace it is up to you (and what your target platform(s) support), but there are many resources/libraries/etc. available to help you. Writing async code requires more care and language/library support than writing plain synchronous code, and once you begin to introduce async patterns into your code, the asynchronicity often proliferates and requires more and more code to maintain consistency and correct behavior. JavaScript at its core does not have great language-level support for async code, but the situation is improving with new language features, like native promises, ES6 generators, libraries like fibers in Node.js, along with hundreds of existing async modules and libraries in repositories like npm.

However, in this article, I would still like to stay at a lower level, and cover two more low-level async coding patterns in JavaScript: events and promises.

Events

Events in JavaScript are a publish-subscribe (pub-sub) mechanism for communicating between JavaScript objects. The idea with events is similar to callbacks - the publisher of an event provides a way for interested parties to subscribe to receive notifications when an event occurs. Subscribing to an event typically means registering a callback function to be invoked by the publisher when the event occurs. When the event occurs, the publisher simply invokes any registered callbacks. Like callbacks, events can occur synchronously or asynchronously, and the event listener callbacks can be invoked synchronously or asynchrounously.

Events are used natively by JavaScript for things like DOM events, like clicks, mouse movements, form submissions etc. Even in non-browser JavaScript, events are used widely, like with Node.js's EventEmitter. In Node.js, events also appear in things like streams.

The main benefit of using events is that they can be consumed by multiple listeners. When an event occurs, the publisher of the event can invoke multiple registered callbacks, so multiple objects can be notified. It also creates loose coupling between components, because the publisher should not care what, or how many consumers are subscribed, and the subscribers do not need to have intimate knowledge of what the publisher is doing internally.

Most larger JavaScript frameworks (browser or non-browser) support some type of eventing, including jQuery, AngularJS, Backbone, React, Ember, and as mentioned before, Node.js, with many varieties of EventEmitters and streams.

Below is a simple example for using an event-based API. This example is implemented in Node.js, using the basic EventEmitter module.

// Get the constructor function for the Node.js EventEmitter
var EventEmitter = require("events").EventEmitter;

// Clock is our event publisher - when started, it will publish a "tick" event
// every second.
function Clock() {
    this.emitter = new EventEmitter();
}

// Starts the clock ticking
Clock.prototype.start = function() {
    var self = this;
    this.interval = setInterval(function() {
        self.emitter.emit("tick", new Date());
    }, 1000);
};

// Stops the clock from ticking
Clock.prototype.stop = function() {
    if (this.interval) {
        clearInterval(this.interval);
        this.interval = null;
    }
};

// Register a callback for the "tick" event
Clock.prototype.onTick = function(callback) {
    this.emitter.on("tick", callback);
};

// Create our clock
var clock = new Clock();

// Register an event for the clock's tick event
clock.onTick(function(date) {
    console.log(date);
});

// Start the clock
clock.start();

This basic Node.js program outputs something like the following:

% node clock.js
Wed Oct 15 2014 14:08:01 GMT-0600 (MDT)
Wed Oct 15 2014 14:08:03 GMT-0600 (MDT)
Wed Oct 15 2014 14:08:04 GMT-0600 (MDT)
Wed Oct 15 2014 14:08:05 GMT-0600 (MDT)
Wed Oct 15 2014 14:08:06 GMT-0600 (MDT)
Wed Oct 15 2014 14:08:07 GMT-0600 (MDT)
Wed Oct 15 2014 14:08:08 GMT-0600 (MDT)
...repeats forever...

Here, the Clock's onTick function allows any number of objects to register a callback to be invoked on every clock tick. In the example, there's only one registered subscriber, but in reality there could be many more.

Events are a useful synchronous or asynchronous communication mechanism, but they do not inherently help to solve the problem of the sequencing of async calls. You can however use other techniques to help with this, like you can with callbacks.

Promises

Promises are another mechanism for dealing with asynchronous communication between JavaScript objects. Promises have become quite popular in JavaScript in the past few years, and there are many Promise implementations available now, including an upcoming native Promise implementation in ECMAScript 6.

Promises are similar to callbacks in that they can be used to notify other components when an async task has completed or failed, but the way this is accomplished is a bit different than callbacks or events. With callbacks, an async API function accepts one or more function arguments, which the API function can invoke when some task has completed or failed; whereas, a Promise-based function does not accept callback arguments, but instead returns an promise object which other components can use to register completion or failure callbacks. Also, another big difference between callbacks and promises, is that the promise object will hold onto the value or error object after fulfillment, so any other module can check the status of the promise, and access the value, even after the promise is settled. With callbacks and events the invoker of the callback or publisher of the event do not typically hold onto the last value, so if an interested party misses an event, there might not be a way to detect that it happened, or check what data may have been sent with the event.

When talking about promises, there is some very specific terminology involved, which is outlined in the Promises/A+ spec. There are other promise specs available too, but Promises/A+ seems to be one of the more (most) popular ones. There are many Promise tutorials available around the internet, so I won't go into a tutorial here, but I did want to provide a quick example on how promises can be used to sequence async function calls. I'll use the powerful and popular promise library Q for the example.

This is a very contrived example, but demonstrates how sequencing of async calls can work with promises:

function begin() {
    console.log("begin");
    return 0;
}

function end() {
    console.log("end");
}

function incrementAsync(i) {
    var defer = Q.defer();

    setTimeout(function() {
        i++;
        console.log(i);
        defer.resolve(i);
    }, 0);

    return defer.promise;
}

Q.fcall(begin)
    .then(incrementAsync)
    .then(incrementAsync)
    .then(incrementAsync)
    .then(end);

This example outputs the following to the console:

begin
1
2
3
end

The main driver of this is example is the Q promise chain, started by the invocation of Q.fcall with the begin function argument. Q.fcall is a static method provided by Q that executes the provided function, and returns a promise of a value. The argument function can either return a promise value or a non-promise value, but either way, Q will return a promise from Q.fcall. Because Q.fcall always returns a promise, you can chain methods onto the promise using the then function, which is the cornerstone method of promise APIs. Methods that return a promise are often said to be "thenable," which means you can chain on callbacks using .then().

The first .then above chains the incrementAsync function to the promise created by Q.fcall(begin). The incrementAsync function takes a numeric argument, sets a timeout to increment the value asynchronously, then returns a promise for the incremented value. The incrementAsync method creates a Q deferred object (using Q.defer()), which is the object that the "producer" of a promise deals with. The producer of a promise is responsible for either fulfilling, or rejecting the promise at some point, typically when an async call succeeds or fails. In Q, the this is done by calling either .resolve() or .reject() on the deferred object. In incrementAsync, the promise is fulfilled by incrementing i, then calling .resolve(i), which indicates that the promise is fulfilled, and provides a value to pass to the next function in the chain. The value passed to .resolve() is passed to the next function in the chain as the first argument to that function. Each method in the Q promise chain can either return a promise for a value or a plain value, and Q will execute the chain in sequence, based on when each successive fulfillment or rejection. Promises do not need to be fulfilled with a value - they can simply be fulfilled with no value to indicate that an async operation succeeded, but there is no value to provide.

The Promises/A+ spec requires that promises are always resolved asynchronously, so the setTimeout in the example above is actually redundant, and only used to emphasize the async nature of incrementAsync.

Promises are a somewhat complex topic, which is difficult to fully cover in one blog post, but there are numerous resources available for further learning.

The future

JavaScript is rapidly evolving as a language and ecosystem. There are many exciting new language features coming, to aid with async code. One of the most exciting features is ES6 generators, which are an extremely powerful and new way to program in JavaScript. I will not get into this topic now, but there are many good tutorials and guides available around the internet.

Conclusion

Async programming is an important concept to grasp in JavaScript, and there are many different ways to embrace it. There is no correct answer in how to deal with async code, but it's important to understand the different options available, so you can choose the right solution for your needs.