Wait for all promises to resolve

107,434

Solution 1

I want the all to resolve when all the chains have been resolved.

Sure, then just pass the promise of each chain into the all() instead of the initial promises:

$q.all([one.promise, two.promise, three.promise]).then(function() {
    console.log("ALL INITIAL PROMISES RESOLVED");
});

var onechain   = one.promise.then(success).then(success),
    twochain   = two.promise.then(success),
    threechain = three.promise.then(success).then(success).then(success);

$q.all([onechain, twochain, threechain]).then(function() {
    console.log("ALL PROMISES RESOLVED");
});

Solution 2

The accepted answer is correct. I would like to provide an example to elaborate it a bit to those who aren't familiar with promise.

Example:

In my example, I need to replace the src attributes of img tags with different mirror urls if available before rendering the content.

var img_tags = content.querySelectorAll('img');

function checkMirrorAvailability(url) {

    // blah blah 

    return promise;
}

function changeSrc(success, y, response) {
    if (success === true) {
        img_tags[y].setAttribute('src', response.mirror_url);
    } 
    else {
        console.log('No mirrors for: ' + img_tags[y].getAttribute('src'));
    }
}

var promise_array = [];

for (var y = 0; y < img_tags.length; y++) {
    var img_src = img_tags[y].getAttribute('src');

    promise_array.push(
        checkMirrorAvailability(img_src)
        .then(

            // a callback function only accept ONE argument. 
            // Here, we use  `.bind` to pass additional arguments to the
            // callback function (changeSrc).

            // successCallback
            changeSrc.bind(null, true, y),
            // errorCallback
            changeSrc.bind(null, false, y)
        )
    );
}

$q.all(promise_array)
.then(
    function() {
        console.log('all promises have returned with either success or failure!');
        render(content);
    }
    // We don't need an errorCallback function here, because above we handled
    // all errors.
);

Explanation:

From AngularJS docs:

The then method:

then(successCallback, errorCallback, notifyCallback) – regardless of when the promise was or will be resolved or rejected, then calls one of the success or error callbacks asynchronously as soon as the result is available. The callbacks are called with a single argument: the result or rejection reason.

$q.all(promises)

Combines multiple promises into a single promise that is resolved when all of the input promises are resolved.

The promises param can be an array of promises.

About bind(), More info here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

Solution 3

Recently had this problem but with unkown number of promises.Solved using jQuery.map().

function methodThatChainsPromises(args) {

    //var args = [
    //    'myArg1',
    //    'myArg2',
    //    'myArg3',
    //];

    var deferred = $q.defer();
    var chain = args.map(methodThatTakeArgAndReturnsPromise);

    $q.all(chain)
    .then(function () {
        $log.debug('All promises have been resolved.');
        deferred.resolve();
    })
    .catch(function () {
        $log.debug('One or more promises failed.');
        deferred.reject();
    });

    return deferred.promise;
}
Share:
107,434

Related videos on Youtube

jensengar
Author by

jensengar

Updated on March 01, 2020

Comments

  • jensengar
    jensengar over 4 years

    So I have a situation where I have multiple promise chains of an unknown length. I want some action to run when all the CHAINS have been processed. Is that even possible? Here is an example:

    app.controller('MainCtrl', function($scope, $q, $timeout) {
        var one = $q.defer();
        var two = $q.defer();
        var three = $q.defer();
    
        var all = $q.all([one.promise, two.promise, three.promise]);
        all.then(allSuccess);
    
        function success(data) {
            console.log(data);
            return data + "Chained";
        }
    
        function allSuccess(){
            console.log("ALL PROMISES RESOLVED")
        }
    
        one.promise.then(success).then(success);
        two.promise.then(success);
        three.promise.then(success).then(success).then(success);
    
        $timeout(function () {
            one.resolve("one done");
        }, Math.random() * 1000);
    
        $timeout(function () {
            two.resolve("two done");
        }, Math.random() * 1000);
    
        $timeout(function () {
            three.resolve("three done");
        }, Math.random() * 1000);
    });
    

    In this example, I set up a $q.all() for promises one, two, and three which will get resolved at some random time. I then add promises onto the ends of one and three. I want the all to resolve when all the chains have been resolved. Here is the output when I run this code:

    one done 
    one doneChained
    two done
    three done
    ALL PROMISES RESOLVED
    three doneChained
    three doneChainedChained 
    

    Is there a way to wait for the chains to resolve?

  • jensengar
    jensengar over 10 years
    That requires me to know the length of my chain though right? I mean if I had a promise of length 10, I would have to do $q.all([p1.then(..).then(...).then(...).then(...) ...]); right?
  • jensengar
    jensengar over 10 years
    Thanks for confirming my worst fear. Now I have to come up with a way to get the last promise lol.
  • Bergi
    Bergi over 10 years
    What's the problem with that? Are your chains dynamically constructed?
  • jensengar
    jensengar over 10 years
    Exactly my problem. I am trying to dynamically create a promise chain then I want to do something when the chain(s) complete.
  • Bergi
    Bergi over 10 years
    Can you show us your code (maybe ask a new question)? Are there items appended to the chain after Q.all was executed - otherwise it should be trivial?
  • jensengar
    jensengar over 10 years
    I would love to show you the code... but I haven't finished writing it yet, however I will do my best to explain it. I have a list of "actions" that need to be done. These actions may have any number levels of sub-actions associated with them. I want to be able to do something when all the actions and their subactions are complete. There will likely be multiple $q.alls, however once I start the resolution process, no new actions/promises will be chained.
  • Bergi
    Bergi over 10 years
    Yeah, you'd recursively descend the tree of actions with multiple all() calls for subactions on each level - then you only need to call act(rootAction).then(…)
  • jensengar
    jensengar over 10 years
    Exactly, I just haven't gotten that far yet or figured out how to build the all() correctly for each level.
  • Bergi
    Bergi over 10 years
    s/each level/each node/ - sorry if that has caused some confusion.
  • nick
    nick over 8 years
    The then method of $q.all is provided an array of the returned promises, so you can loop that array and call then on each item in the array, as opposed to calling then when you add the promise to promise_array.
  • Anastasia
    Anastasia over 8 years
    It's not jQuery.map() but Array.prototype.map() (developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…‌​) but this approach works.