Last Updated: March 21, 2023
Β·
144.1K
Β· amoniker

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

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

over 1 year ago Β·

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 Β·

@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 Β·

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 Β·

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

over 1 year ago Β·

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 Β·

Thanks @fkiller - I'm glad to help!

over 1 year ago Β·

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 Β·

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

over 1 year ago Β·

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 Β·

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 Β·

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 Β·

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 Β·

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 Β·

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 Β·

@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 Β·

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 Β·

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 Β·

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

over 1 year ago Β·

This helped me a lot. Thank you!

over 1 year ago Β·

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 Β·

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 Β·

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

over 1 year ago Β·