The async library encapsulates a couple of very common asynchronous patterns, including concurrent asynchronous calls and repeating asynchronous asynchronous lists. It is designed to work with the nodeback APIs (err, res) , which makes it useful for many Node.js applications. async , however, is a specific solution and simplifies only the asynchronous templates included in the library.
Promises, by contrast, is, in my opinion, a much more general solution to the asynchronous code problem. Not only do they provide obvious benefits at a glance at the error bubbles and flattening callback pyramids, problems that would otherwise require sorting complex async patterns, encapsulation can be solved much easier.
I will demonstrate this with a quick look at some of the available async templates. For example, the async.waterfall function async.waterfall used like this:
async.waterfall([ function (cb) { asyncCall('argument', cb); }, function(resultOfFirstCall, cb) { anotherCall(resultOfFirstCall, 'someOtherArgument' cb); }, ], function(err, res) { if (err) handle(err); useFinalResult(res); });
There is no equivalent of async.waterfall in most promise libraries (or at least not in Q) because it is so simple to implement it from scratch using Array.reduce like this (Q-based example, but pretty much the same most in other promise libraries):
[ function() { return asyncCall('argument'); }, function(resultOfFirstCall) { return anotherCall(resultOfFirstCall, 'someOtherArgument'); } ].reduce(Q.when, Q()) .then(useFinalResult, handle);
Other great features in async include async.parallel , which Q includes as Q.all :
// async async.parallel([ asyncFunc, asyncFunc2 ], function(err, res) { if (err) handle(err); useFinalResult(res); // res[0] === asyncFuncResult // res[1] === asyncFunc2Result }); // Q Q.all([ asyncFunc(), asyncFunc2() ]).then(useFinalResult, handle);
And async.map . You really don't need async.map when you use promises, because normal Array.map enough:
// async async.map(['file', 'file2', 'file3'], fs.stat, function(err, res) { if (err) handle(err); useFinalResult(res); }); // Q Q.all(['file', 'file2', 'file3'] .map(Q.nfbind(fs.stat))) .then(useFinalResult, handle);
The rest of async also easily implemented briefly, using the relatively simple parts of your promise library. (Note that in the last example, the Q.nfbind : nfbind function was used, and the rest of the nf* Q functions are basically all you need to use promises with nodeback APIs, so there is even much resistance to using promises with libraries waiting for nodebacks .)
In the end, whether you use promises or nodebacks, but I think promises are a much more flexible, capable and generally compressed way to implement most asynchronous operations.
Callbacks are required, promises are functional, deserve attention for more information in this common vein.