Geison Santos
Geison Santos

Reputation: 197

$q.all aborts all promises when gets a 404 response

I have a set of promises that I want to be run simultaneously. I'm using $q.all in order to achieve that. When one promise returns a 404 response any other one are unfulfilled. I want to be able to get an error promise individually on $q.all function. How do I do this?

var idPais = 13, idUF = 20;
var promises = [
    PaisAPI.list(), 
    UnidadeFederativaAPI.list(idPais), 
    MunicipioAPI.list(idUF)
];

$q.all(promises).then(
    function(values) {
        var paises = values[0].data;
        var ufs = values[1].data;
        var municipios = values[2].data;

        console.log('paises ', paises.length);
        console.log('ufs ', ufs.length);
        console.log('municipios ', municipios.length);

        $scope.paises = paises;
        $scope.unidadesFederativas = ufs;
        $scope.municipios = municipios;
    },
    function(error) {
        if (error.status == 500) {
            alert('Server error');
        }
        //when one of the promises get a 404 response any other one are unfulfilled
    }
);

Upvotes: 0

Views: 1383

Answers (3)

Duncan
Duncan

Reputation: 95692

You need to ensure that the errors which shouldn't make $q.all() fail are all handled by the individual promises. You should be sure to handle all of the error cases that you want to allow the $q.all to complete normally, but if there are other errors possible which should still abort the $q.all then those should be passed through using $q.reject. For example to return a length of -1 on an error 404:

function handle404(error) {
    if (error.status == 404) {
        return { data: { length: -1 }};
    }
    return $q.reject(error)
}
var promises = [
    PaisAPI.list().catch(handle404), 
    UnidadeFederativaAPI.list(idPais).catch(handle404), 
    MunicipioAPI.list(idUF).catch(handle404)
];

then the rest of the code as before. Or to just return an empty array in those cases:

function handle404(error) {
    if (error.status == 404) {
        return { data: []};
    }
    return $q.reject(error)
}

but that wouldn't be distinguishable from getting back an empty array with no error.

Upvotes: 1

georgeawg
georgeawg

Reputation: 48968

Re-factor the code to put the values on $scope before executing $q.all:

var idPais = 13, idUF = 20;
var promiseObj = {};

promiseObj.paises = PaisAPI.list()
  .then(function (response) {
    var paises = response.data;
    console.log('paises ', paises.length);
    $scope.paises = paises;
    return paises;
}).catch(angular.identity);

promiseObj.ufs = UnidadeFederativaAPI.list(idPais)
  .then(function (response) { 
    var ufs = response.data;
    console.log('ufs ', ufs.length);
    $scope.unidadesFederativas = ufs;
    return ufs;
}).catch(angular.identity);

promiseObj.municipos = MunicipioAPI.list(idUF)
  .then(function (response) {
    var municipios = response.data;
    console.log('municipios ', municipios.length);
    $scope.municipios = municipios;
    return municipios;
}).catch(angular.identity);

The .catch(angular.identity); converts a rejected promise to a fulfilled promise. Each API call executes in parallel.

Use $q.all to wait for all promises to complete either fulfilled or rejected:

$q.all(promiseObj).then(function (dataObj) {
     console.log("All API calls complete");
     if (dataObj.paises.status) {
         console.log("paises failed with ",dataObj.paises.status);
     };
     if (dataObj.ufs.status) {
         console.log("ufs failed with ",dataObj.ufs.status);
     };
     if (dataObj.municipos.status) {
         console.log("municipos failed with ",dataObj.municipos.status);
     };
}); 

One of the things that sets $q.all apart from other promise libraries is that AngularJS $q.all accepts an object hash of promises.

For more information, see AngularJS $q Service API Reference - $q.all

Upvotes: 2

curveball
curveball

Reputation: 4505

I suppose it is default behavior in accordance with Promises/A+ spec on which all this stuff is built upon.

Documention for JS native promises states:

The Promise.all() method returns a single Promise that resolves when all of the promises in the iterable argument have resolved or when the iterable argument contains no promises. It rejects with the reason of the first promise that rejects.

From angular docs for all():

Returns a single promise that will be resolved with an array/hash of values, each value corresponding to the promise at the same index/key in the promises array/hash. If any of the promises is resolved with a rejection, this resulting promise will be rejected with the same rejection value.

All implementations across different frameworks should follow and actually follow this approach.

As a workaround, If your code returns 404 error after, lets say, an ajax call, try to resolve it too instead of rejecting. I mean treat this error as a positive outcome and resolve it with some special value like false, {error: 404, message: 'Not Found'} or 0 - to show difference for the client code later. Resolving instead of rejecting should make all() wait untill all the promises are done.

Upvotes: 3

Related Questions