Tuesday, March 27, 2012

Taking Toddler Steps with Node.js - Thou Shalt Not Deny Asynchrony

A few of weeks ago, I ran into this awesome article “Understanding process.nextTick()” on the How To Node blog. In this article, the ever friendly Kishore Nallan shows a couple of common use cases for the process.nextTick() function of the core Node.js library. You should read this blog post three times, at the very least.

What I found to be particularly interesting at the time was the part on how to keep callbacks truly asynchronous. I admit that this is something that I’ve been struggling with for a while. At first I thought this was not a big deal, but this can become quite troublesome in your applications if you’re not careful.

Let’s look at some code.

var fileSystem = require('fs');

module.exports = (function() {
    function Configuration() {}
    Configuration.settings = null;

    Configuration.getSettings = function(callback) {
        var self = this;
        if(this.settings) {
            return callback(null, self.settings);
        }

        fileSystem.readFile(__dirname + '/config.json', 'utf8', 
            function(error, data) {
                var self = this;
                if(error) {
                    return callback(error);
                }

                this.settings = JSON.parse(data);
                return callback(null, self.settings);
        });
    };

    return Configuration;
})();

Here we’re looking at a simple module that exposes a single function named getSettings. When called for the first time, this function reads the content of a JSON file and stores the result in a static property. For all successive function calls, we simply serve the object stored in the static property without reading the JSON file again. This all looks very simple and everything works great. But this code is also highly inconsistent!

Let’s have a closer look.

When reading the content of the JSON file, we invoke the callback, specified as a parameter to getSettings, inside another callback (the anonymous function specified to the readFile function). This means that the invocation of the specified callback is asynchronous because the callback of the readFile function is also asynchronous. Nothing wrong here.

When calling the getSettings function a second time, we instantly invoke the specified callback with the object stored in the static variable. This means that the callback is executed synchronously. From the perspective of the client of our module, this is inconsistent behavior. The callback specified by the client code is executed asynchronously on the first call while it is executed synchronously on all successive calls.

In order to fix this inconsistent behavior, we can use the process.nextTick() function which defers the execution of our callback until the next iteration of the event loop.

Let’s have a look at the code that fixes this issue:

if(this.settings) {
    return process.nextTick(function() {
        return callback(null, self.settings);
    });
}

This is something that you need to think about when invoking callback functions. 

Until next time.

No comments: