Promise Chains with Node.js
Promises are an excellent way to reduce the chances of being exiled to callback hell. In node.js I've been using the promising module Q to handle the flow of asynchronous code.
If you have a number of things you'd like to do in a specific order, you can set up a promise chain by using something like:
first_promise()
.then(function() { return second_promise(); })
.then(function() { return third_promise(); })
...
.then(function() { return nth_promise(); });
where *_promise() functions contain some behavior and return a promise object which swears to eventually return a result. Once the real result is returned, it will set off the next function in the chain.
Ok cool. This is a lot easier to read than a bunch of nested callbacks.
What happens if we want to set off an asynchronous function and wait until we get a result before executing the behavior of the next promise?
Q solves this with deferred promises:
function get_the_async_data() {
var deferred = Q.defer();
async_function(arguments, function(result) {
deferred.resolve(result);
});
return deferred.promise;
}
Ok cool. Now we can set up a chain of asynchronous events that each depend on the execution of the previous one.
What if we'd like to set off a bunch of async calls at once, and wait until they all finish (at various times, in any order) to set off the next promise in the chain?
Q provides a simple method that takes an array of promises: Q.all()
function get_all_the_things(things) {
var the_promises = [];
things.forEach(function(thing) {
var deferred = Q.defer();
get_a_thing(thing, function(result) {
deferred.resolve(result);
});
the_promises.push(deferred.promise);
});
return Q.all(the_promises);
}
Now the next function in our chain will wait until every deferred promise that got created gets resolved. Good stuff.
Lastly, we might want a promise chain based on a variable number of operations whose order matters. For that, we could do something like this:
// create an empty promise to begin the chain
var promise_chain = Q.fcall(function(){});
// loop through a variable length list
// of things to process
async_operations.forEach(function(async_op) {
var promise_link = function() {
var deferred = Q.defer();
perform_async_op(async_op, function(result) {
deferred.resolve(result);
});
return deferred.promise;
};
// add the link onto the chain
promise_chain = promise_chain.then(promise_link);
});
If you do this inside of a function that's already part of another promise chain, you can then:
return promise_chain;
and the main chain will not continue until the variable-length sub-chain has been resolved.
Neat.
Written by Jim Greenleaf
Related protips
24 Responses
@damienklinnert Thanks! I'm glad it was helpful :)
Very nice post. As a side note, instead of writing loops like:
for (var i = 0; i < async_operations.length; i++) {
(function(i) {
//...
})(i);
});
You should consider using forEach
instead:
async_operations.forEach(function(op) {
//...
});
@n1k0 Great point! That's much easier to read and avoids the need to create additional closures. I updated the examples with this.
You're welcome :) Now I'm looking back at the resulting code, I wonder if using map()
wouldn't be even more concise:
function get_all_the_things(things) {
return Q.all(things.map(function(thing) {
var deferred = Q.defer();
get_a_thing(thing, function(result) {
deferred.resolve(result);
});
return deferred.promise;
}));
}
@n1k0 You may be right. I'll leave that as an exercise to the reader ;)
Thank you. It helps a lot to understand more detail of Q. (Examples of Q site don't make sense to me :b)
Thanks @fkiller - I'm glad to help!
What do you think about this:
.each(parameterstoasyncfunction, function(parametertoasyncfunction, i){
var deferred = Q.defer();
var add = Q.nbind(theasyncfunction,context);
promises.push(add(parametertoasyncfunction));
});
</code></pre>
That's a cool way to do it, @futbolpal - I assume that deferred
would be resolved within the_async_function
?
why not use eventproxy,if u use a lib with nodejs style ( function (err,cb)),so u may need write a lot of nest function use deferred if u choose use Q.eg,if u use mongoose ,but mongoose just provide traditional nodejs callback style interface for u,so u wanna promise style code,u many need do something to wrapper the origin mogoose function first.
Hi,
How this Q can be used get promises in mongoose async function like:
User.findOne({ Email: userEmail }, function (err, user) {
if (err) {
resultMessage = 'No user found for username: ' + userEmail;
reject([resultMessage,0]);
} else {
resolve([user,1]);
}
});
The findOne method gets 2 arguments like err and user, where as in examples everywhere there is no second argument.
So how can I resolve promise separately for error and success conditions?
Hey @sandesh27
It seems like you're on the right track there. You could create a new var deferred = Q.defer();
before calling User.findOne
, then you could call deferred.resolve()
or deferred.reject()
inside the error and success conditions. Just make sure to use the deferred.promise
in your flow.
Hello Jim, I currently have a for loop that takes the form of
"for (i=0; i < 112; i=i+2) {
do stuff
}
However, i also need to return promises after the for loop is done, I am not sure how I can do that :(
Any help is appreciated! Thanks!!
Hello @sally-yang-jing-ou
Could you give a little more detail on what you're trying to accomplish? Do you need to assemble a promise chain within the for loop, or return a promise afterward, or maybe resolve one afterward?
Oh god, sorry for the spam >.< My laptop froze and I might have pressed the "submit" button too many times >.<! Sorrrrry!!
@sally-yang-jing-ou, haha it's ok - I got eight emails for your comment, but only one comment appears in the thread so I think you discovered a Coderwall bug :)
As for returning a promise, it doesn't seem like _.bind is what's expecting the promise, but each of the .then()
functions likely is.
If you're using Q you could do something like this: http://pastebin.com/G2iGrxmt
If you're using a different promise library, the deferred calls would change, but the principle would be the same.
Oh right, sorry, it's .then() that's expecting a promise.
And thank you so much for the help! I'll try this out soon :)!
great post! i was looking for a smart way to return a large amount of dynamically generated promises back up the chain and using a promise chain is a great way to do so.
Thanks @steveinatorx, I'm glad the chain pattern was helpful!
This helped me a lot. Thank you!
what is performasyncop? Building a promise chain in this fashion cannot be sequential. they will fire in sequence, but not wait for resolve to actually resolve before firing the next call.
Hi,
I need to loop through an array of promises(function returns deffered.promise) and wait for one to complete before going to next. Could anyone please help here?
@sunnyfrancis - see the last example. It shows how to do exactly what you're trying to do.