First of all good question. This is something we (at least I) deal with with promises often. It's also a place where promises really shine over callbacks in my opinion.
What's going on here basically is that you really want two things that your library doesn't have:
.spread
that takes a promise that returns an array and changes it from an array parameter to parameters. This allows cutting things like .then(result) { var postsA = result[0], postsB = result[1];
into .spread(postsA,postsB
.
.map
that takes an array of promises and maps each promise in an array to another promise - it's like .then
but for each value of an array.
There are two options, either use an implementation that already uses them like Bluebird which I recommend since it is vastly superior to the alternatives right now (faster, better stack traces, better support, stronger feature set) OR you can implement them.
Since this is an answer and not a library recommendation, let's do that:
Let's start with spreading, this is relatively easy - all it means is calling Function#apply
which spreads an array into varargs. Here is a sample implementation I stole from myself:
if (!Promise.prototype.spread) {
Promise.prototype.spread = function (fn) {
return this.then(function (args) {
//this is always undefined in A+ complaint, but just in case
return fn.apply(this, args);
});
};
}
Next, let's do mapping. .map
on promises is basically just Array mapping with a then:
if(!Promise.prototype.map){
Promise.prototype.map = function (mapper) {
return this.then(function(arr){
mapping = arr.map(mapper); // map each value
return Promise.all(mapping); // wait for all mappings to complete
});
}
}
For convenience, we can introduce a static counterpart of .map
to start chains:
Promise.map = function(arr,mapping){
return Promise.resolve(arr).map(mapping);
};
Now, we can write your code like we actually want to:
var names = ["usernameA","usernameB"]; // can scale to arbitrarily long.
Promise.map(names, getUsername).map(getPosts).spread(function(postsA,postsB){
// work with postsA,postsB and whatever
});
Which is the syntax we really wanted all along. No code repetition, it's DRY, concise and clear, the beauty of promises.
Note that this doesn't scratch the surface of what Bluebird does - for example, Bluebird will detect it's a map chain and will 'push' functions on to the second request without the first one even finishing, so the getUsername
for the first user won't wait to the second user but will actually call getPosts
if that's quicker, so in this case it's as fast as your own gist version while clearer imo.
However, it is working, and is nice.
Barebones A+ implementations are more for interoperability between promise libraries and are supposed to be a 'base line'. They're useful when designing specific platform small APIs - IMO almost never. A solid library like Bluebird could significantly reduce your code. The Promise library you're using, even says in their documentation:
It is designed to get the basics spot on correct, so that you can build extended promise implementations on top of it.