Wait for all promises to resolve
Asked Answered
A

6

108

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?

Aguascalientes answered 13/2, 2014 at 16:5 Comment(0)
R
165

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");
});
Raphael answered 13/2, 2014 at 17:47 Comment(8)
Thanks for confirming my worst fear. Now I have to come up with a way to get the last promise lol.Aguascalientes
What's the problem with that? Are your chains dynamically constructed?Raphael
Exactly my problem. I am trying to dynamically create a promise chain then I want to do something when the chain(s) complete.Aguascalientes
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?Raphael
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.Aguascalientes
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(…)Raphael
Exactly, I just haven't gotten that far yet or figured out how to build the all() correctly for each level.Aguascalientes
s/each level/each node/ - sorry if that has caused some confusion.Raphael
K
16

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

Ketone answered 26/2, 2015 at 18:8 Comment(1)
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.Conchology
C
4

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;
}
Conversable answered 18/11, 2015 at 10:46 Comment(1)
It's not jQuery.map() but Array.prototype.map() (developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…) but this approach works.Adjudge
C
0

There is a way. $q.all(...

You can check the below stuffs:

http://jsfiddle.net/ThomasBurleson/QqKuk/

http://denisonluz.com/blog/index.php/2013/10/06/angularjs-returning-multiple-promises-at-once-with-q-all/

Codeclination answered 13/2, 2014 at 16:12 Comment(1)
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?Aguascalientes
E
0

You can use "await" in an "async function".

app.controller('MainCtrl', async function($scope, $q, $timeout) {
  ...
  var all = await $q.all([one.promise, two.promise, three.promise]); 
  ...
}

NOTE: I'm not 100% sure you can call an async function from a non-async function and have the right results.

That said this wouldn't ever be used on a website. But for load-testing/integration test...maybe.

Example code:

async function waitForIt(printMe) {
  console.log(printMe);
  console.log("..."+await req());
  console.log("Legendary!")
}

function req() {
  
  var promise = new Promise(resolve => {
    setTimeout(() => {
      resolve("DARY!");
    }, 2000);
    
  });

    return promise;
}

waitForIt("Legen-Wait For It");
Evolutionary answered 28/2, 2018 at 17:14 Comment(0)
C
0

I got this example used in the ocLazyLoad library:

var promises = [];
angular.forEach(paths, function loading(path) {
    promises.push(buildElement('css', path, params));
});
$q.all(promises).then(function success() {
    callback();
 }, function error(err) {
        callback(err);
  });
Cuffs answered 20/3, 2023 at 1:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.