Taking Toddler Steps with Node.js – The Towering Inferno Revisited

Soon after I started using Node.js, I ran into the phenomenon of multiple nested callbacks that create some kind of horizontal tower effect. The solution I came up with in order to improve the readability of my code was using a library called step, as described in this blog post that I wrote at that time.

Over the past years I switched over to a couple of other control flow libraries that solve the same problem as step, but  eventually I settled on using the async library.

Let’s look back at the problem I used in my original blog post:

image

Here’s the slightly refactored equivalent using async:

http.createServer(function(request, response) {
    async.waterfall([
        assembleFilePath,
        readFavoritePodcastsFromFile,
        addNewFavoritePodcastToFile
        ], 
        function(error, favoritePodcasts) {
            if(error)
                return response.end(error);

            response.writeHead(200, {
                'Content-Type': 'text/html', 
                'Content-Length': favoritePodcasts.length
            });

            response.end(favoritePodcasts); 
        }
    );
})
.listen(2000);

function assembleFilePath(callback) {
    var filePath = path.join(__dirname, 'podcasts.txt');
    callback(null, filePath);
}

function readFavoritePodcastsFromFile(podcastsFilePath, callback) {
    fileSystem.readFile(podcastsFilePath, 'utf8', function(error, data) {
        if(error)
            return callback(error);

        callback(null, podcastsFilePath, data);
    });                     
}

function addNewFavoritePodcastToFile(podcastsFilePath, favoritePodcastData, callback) {
    var favoritePodcasts = favoritePodcastData;

    if(-1 == favoritePodcasts.indexOf('Astronomy Podcast')) {
        favoritePodcasts = favoritePodcasts + '\n' + 'Astronomy Podcast';       
        fileSystem.writeFile(podcastsFilePath, favoritePodcasts, function(error) {
            if(error)
                return callback(error);

            callback(null, favoritePodcasts);
        });                     
    }
    else {
        process.nextTick(function() {
            callback(null, favoritePodcasts);
        });     
    }
}

 

Here I’ve used the waterfall method of the async library in order to pass results from one function to the next. Other functions that I often use are series and parallel. Notice that in the addNewFavoritePodcastToFile function I used process.nextTick instead of just invoking the callback. This is done in order to prevent inconsistent behavior of the function. I also wrote about this in the past.

There has been a lot of buzz lately around promises, so I decided to drink some of this kool-aid. Basically, we can achieve the same kind of solution as with the async library.

http.createServer(function(request, response) {

    assembleFilePath()
    .then(readFavoritePodcastsFromFile)
    .then(addNewFavoritePodcastToFile)
    .then(function(favoritePodcasts) {
        response.writeHead(200, {
            'Content-Type': 'text/html', 
            'Content-Length': favoritePodcasts.length
        });

        response.end(favoritePodcasts); 
    })
    .done();
})
.listen(2000);

function assembleFilePath() {
    return Q.fcall(function() {
        return path.join(__dirname, 'podcasts.txt');
    });
}

function readFavoritePodcastsFromFile(podcastsFilePath) {
    var deferred = Q.defer();

    fileSystem.readFile(podcastsFilePath, 'utf8', function(error, favoritePodcasts) {
        if(error)
            return deferred.reject(new Error(error));

        deferred.resolve({
            favoritePodcasts: favoritePodcasts,
            podcastsFilePath: podcastsFilePath
        });
    });

    return deferred.promise;
}

function addNewFavoritePodcastToFile(data) {
    var deferred = Q.defer(),
    favoritePodcasts = data.favoritePodcasts;

    if(-1 == favoritePodcasts.indexOf('Astronomy Podcast')) {
        favoritePodcasts = favoritePodcasts + '\n' + 'Astronomy Podcast';       
        fileSystem.writeFile(data.podcastsFilePath, favoritePodcasts, 
        function(error) {
            if(error)
                return deferred.reject(new Error(error));

            deferred.resolve(favoritePodcasts);
        });                     
    }
    else {
        process.nextTick(function() {
            deferred.resolve(favoritePodcasts);
        });
    }

    return deferred.promise;
}

 

I’ve used the Q library for this code sample. For an excellent introduction to promises and the Q library, check out this great article on the StrongLoop blog. I think the approach using promises looks, uhm … promising as well.

Are you, dear reader, using a control flow library, which one and why?

Until next time.

Published by

Jan Van Ryswyck

Hi, thank you for visiting my blog and reading all the crap that I'm posting here. I'm a senior software engineer at SD WORX. Developing software is one of my greatest passions in life, and I enjoy doing it every single day. I've got three kids (Len, Lisa & Laura) who constantly remind me that there is more in life than just programming all day. They are the greatest kids in the whole world. And last but not least, there's my girlfriend who is my inspiration in life. You can always contact me at jan_dot_van_dot_ryswyck at gmail.com

2 thoughts on “Taking Toddler Steps with Node.js – The Towering Inferno Revisited”

    1. This has been on my list for a while, but I think I’ll have to revisit it. Thanks for pointing this out.

Comments are closed.