I'm learning Node.js development, and trying to wrap my brain around strategies for managing asynchronous "callback hell". The two main strategies I've explored are Caolan McMahon's async module, and Kris Kowal's promise-based Q module.
Like many other people, I'm still struggling to understand when you should use one vs. the other. However, generally speaking I have found promises and Q-based code to be slightly more intuitive, so I have been moving in that direction.
However, I'm still stuck using the async module's functions for managing collections. Coming from a Java and Python background, most of the time when I work with a collection, the logic looks like this:
In client-side JavaScript, I've grown accustomed to using jQuery's map() function... passing in that step #2 logic, and getting the step #3 result as a return value. Feels like the same basic approach.
The Node-side async module has similar map and concat functions, but they don't return the concatenated result back at the original scope level. You must instead descend into the callback hell to use the result. Example:
var deferred = Q.defer();
...
var entries = [???]; // some array of objects with "id" attributes
async.concat(entries, function (entry, callback) {
callback(null, entry.id);
}, function (err, ids) {
// We now have the "ids" array, holding the "id" attributes of all items in the "entries" array.
...
// Optionaly, perhaps do some sorting or other post-processing on "ids".
...
deferred.resolve(ids);
});
...
return deferred.promise;
Since my other functions are becoming promise-based, I have this code returning a promise object so it can be easily included in a then()
chain.
The ultimate question that I'm struggling to articulate is: do I really need both async and Q in the code example above? I'm learning how to replace the async module's control flow with Q-style promise chains generally... but it hasn't yet "clicked" for me how to do mapping or concatenation of collections with a promise-based approach. Alternatively, I'd like to understand why you can't, or why it's not a good idea.
If async and Q are meant to work together as I am using them in the example above, then so be it. But I would prefer not to require the extra library dependency if I could cleanly use Q alone.
(Sorry if I'm missing something outrageously obvious. The asynchronous event-driven model is a very different world, and my head is still swimming.)
Do I really need both?
No. Mapping asynchronous iterators over a collection is quite simple with promises, but it requires two steps instead of one function call. First, the collection is map
ped to an array of promises for the parallel iteration. Then, those promises are fed into Q.all
to make one promise for the mapped collection. In contrast to async
, the order of the result is guaranteed.
var entries = […]; // some array of objects with "id" attributes
var promises = entries.map(function(object) {
return asyncPromiseReturingFunction(object);
}); // the anonymous wrapper might be omitted
return Q.all(promises);
For concat
, you would have to append a
.then(function(results) {
return Array.prototype.concat.apply([], results);
});