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:
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.
Have you looked at https://github.com/Sage/streamlinejs? It’s a js precompiler that essentially gives you async/await style programming in Javascript. Pretty cool.
This has been on my list for a while, but I think I’ll have to revisit it. Thanks for pointing this out.