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:
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) {
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);
});
}
}
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.