ijy61g
Last Updated: April 19, 2017
·
93.27K
· amoniker
Avatar closeup

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.

24 Responses
Add your response

4167
Avatar closeup

@damienklinnert Thanks! I'm glad it was helpful :)

over 1 year ago ·
4234
C0240424425f5ca75071b478c0dc29b9

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) {
     //...
});
over 1 year ago ·
4273
Avatar closeup

@n1k0 Great point! That's much easier to read and avoids the need to create additional closures. I updated the examples with this.

over 1 year ago ·
4285
C0240424425f5ca75071b478c0dc29b9

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;
    }));
}
over 1 year ago ·
4326
Avatar closeup

@n1k0 You may be right. I'll leave that as an exercise to the reader ;)

over 1 year ago ·
6462

Thank you. It helps a lot to understand more detail of Q. (Examples of Q site don't make sense to me :b)

over 1 year ago ·
6463
Avatar closeup

Thanks @fkiller - I'm glad to help!

over 1 year ago ·
7882
Aa4d16a18421124ad580baf49694f1cb

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>
over 1 year ago ·
7885
Avatar closeup

That's a cool way to do it, @futbolpal - I assume that deferred would be resolved within the_async_function?

over 1 year ago ·
13450
6158ad9226cd4626d24f8b48b1b54e85

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.

over 1 year ago ·
15012
0 1c0r3cwyi4tln2fupioe3n7kdj8yq7culhik3nah94nuiihrxkfc7qsafph be9b tjla1pzztha

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?

over 1 year ago ·
15013
Avatar closeup

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.

over 1 year ago ·
15177
3918c4d078c32e37ffbde7f1451f5d2c

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!!

over 1 year ago ·
15180
Avatar closeup

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?

over 1 year ago ·
15187
3918c4d078c32e37ffbde7f1451f5d2c

Oh god, sorry for the spam >.< My laptop froze and I might have pressed the "submit" button too many times >.<! Sorrrrry!!

over 1 year ago ·
15188
Avatar closeup

@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.

over 1 year ago ·
15217
3918c4d078c32e37ffbde7f1451f5d2c

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 :)!

over 1 year ago ·
16230
9f3e3b6c9f3c5e17e221e5eb01298302

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.

over 1 year ago ·
16340
Avatar closeup

Thanks @steveinatorx, I'm glad the chain pattern was helpful!

over 1 year ago ·
17602
06d1ac58fae56709d0fbf0c4b7e9d47f

This helped me a lot. Thank you!

over 1 year ago ·
18871
None

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.

over 1 year ago ·
26589
0 uqf0z ggy a jzm3zhquzhucoqugrn43jx18zhsj357 x ftc8c198a1rarjzljds5l3bbkgxwxr

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?

over 1 year ago ·
26607
Avatar closeup

@sunnyfrancis - see the last example. It shows how to do exactly what you're trying to do.

over 1 year ago ·