What happens with $q.all() when some calls work and others fail?
Asked Answered
E

7

66

What happens with $q.all() when some calls work and others fail?

I have the following code:

    var entityIdColumn = $scope.entityType.toLowerCase() + 'Id';
    var requests = $scope.grid.data
      .filter(function (rowData, i) {
          return !angular.equals(rowData, $scope.grid.backup[i]);
      })
      .map(function (rowData, i) {
          var entityId = rowData[entityIdColumn];
          return $http.put('/api/' + $scope.entityType + '/' + entityId, rowData);
      });
    $q.all(requests).then(function (allResponses) {
        //if all the requests succeeded, this will be called, and $q.all will get an
        //array of all their responses.
        console.log(allResponses[0].data);
    }, function (error) {
        //This will be called if $q.all finds any of the requests erroring.
        var abc = error;
        var def = 99;
    });

When all of the $http calls work then the allResponses array is filled with data.

When one fails the it's my understanding that the second function will be called and the error variable given details.

However can someone help explain to me what happens if some of the responses work and others fail?

Endear answered 13/11, 2013 at 3:51 Comment(2)
The goal of $q.all is to perform calls in parallel but handle all results simultaneously, so if one is missing it aborts immediately. Think of it as some kind of boolean AND, if one is false the result will be false.Greataunt
Similar: #21626751Greataunt
D
42

I believe since the promise library is based on Q implementation, as soon as the first promise gets rejected, the reject callback is called with the error. It does not wait for other promises to resolved. See documentation of Q https://github.com/kriskowal/q. For Q.all this is what is mentioned

The all function returns a promise for an array of values. When this promise is fulfilled, the array contains the fulfillment values of the original promises, in the same order as those promises. If one of the given promises is rejected, the returned promise is immediately rejected, not waiting for the rest of the batch.

Dispassionate answered 13/11, 2013 at 4:16 Comment(2)
I think this might be a problem for me. My put requests return data with a new row version number. If that data is not available then the next time I try to do an update that update may fail :-( Is there any way that you could think of how I could get around this problem?Endear
Read the documentation here docs.angularjs.org/api/ng.$q . See if finally can help you. Maybe you need to break promises down into groups.Dispassionate
C
41

It's been a while since this question was posted, but maybe my answer might still help someone. I solved a similar problem on my end by simply resolving all promises, but with a return I could process later and see if there were any errors. Here's my example used to preload some image assets:

var loadImg = function(imageSrc) {
    var deferred = $q.defer();

    var img = new Image();
    img.onload = function() {
        deferred.resolve({
            success: true,
            imgUrl: imageSrc
        });
    };
    img.onerror = img.onabort = function() {
        deferred.resolve({
            success: false,
            imgUrl: imageSrc
        });
    };
    img.src = imageSrc;

    return deferred.promise;
}

Later I can see which ones are errorious:

var promiseList = [];
for (var i = 0; i < myImageList.length; i++) {
    promiseList[i] = loadImg(myImageList[i]);
}
$q.all(promiseList).then(
    function(results) {
        for (var i = 0; i < results.length; i++) {
            if (!results[i].success) {
                // these are errors
            }
        }
    }
);
Cordilleras answered 2/6, 2014 at 10:0 Comment(3)
Really a Nice one. :)Butene
Nice idea! general explanation: the approach here is to running async functions (that may resolve/reject and you want them all) behind another temp async functions that only "resolves" and use $q.all for them, instead of the original async function.Hollandia
Should have been "erroneous" but not gonna correct myself after almost 5 years :DCordilleras
R
6

Edit: Only supported in Kris Kowal's Q - but still a useful tidbit

If you want to process all of them without rejecting right away on failure use allSettled

Here's what the docs say:

If you want to wait for all of the promises to either be fulfilled or rejected, you can use allSettled.

Q.allSettled(promises)
.then(function (results) {
    results.forEach(function (result) {
        if (result.state === "fulfilled") {
            var value = result.value;
        } else {
            var reason = result.reason;
        }
    });
});
Rubicon answered 1/10, 2014 at 12:40 Comment(1)
allSettled is part of kriskowal's Q but not part of the Angular $q implementation.Unilobed
T
5

Here is a small answer to it. In this fiddle you can see how it works, if an error occurs in some promise.

$q.all([test1(), test2()]).then(function() {
  // success
}, function() {
  // error
});

http://jsfiddle.net/wd9w0ja4/

Templas answered 18/8, 2015 at 7:37 Comment(1)
fiddle is a great way to think through $q.allSeigniory
S
4

I've found a new angular package which add the allSettled functionality to $q in angular:

https://github.com/ohjames/angular-promise-extras

Substandard answered 14/8, 2015 at 10:55 Comment(0)
G
1

In my case I needed to know when last promise has been resolved no matter if successful or fail. $q.all was not an option because if one fails it goes down immediately. I needed this to make sure user will be redirected no matter what but only if all data are processed (or not) so they can be loaded on next page. So I ended up with this:

  1. Each promise/call implemented also fail callback where "redirect" function is called in both success and fail callbacks.
  2. In this function counter is set, which is increased with each call. If this reaches the number of promises/calls, redirect to next view is made.

I know it's quite a lame way to do it but it worked for me.

Gathering answered 30/11, 2016 at 14:52 Comment(0)
A
1

Could you not simply handle the error condition on your $http promises before passing them to $q? Promises are chained, so this should work:

return $http.put('/api/' + $scope.entityType + '/' + entityId, rowData).then(function(r){return r;}, angular.noop);

Obviously you could change the noop into any transformation you want but this prevents the rejection which prevents $q.all from failing.

Araminta answered 20/6, 2017 at 0:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.