Reputation: 1565
I am geocoding some addresses, sometimes some of them fail. I'd like to be able to get the rest of the results and ignore the failed one so I can display the other coordinates on a map. Currently $q.all will call the errorHandler when one is rejected, so I lose the results of other promises.
$q.all(promises).then(function(coords) {
for (var j = 0;j<coords.length;j+=1) {
//success code
}
}, function(error) {
console.log("Failed",error.type,error.message);
});
Upvotes: 5
Views: 3640
Reputation: 368
You can resolve your problem using only the implementation of Angular what you have to do is isolate the async calls in other function which is going to return a promise and this promise is going to be managed for you.
Let's use a example, let's say that you have four async calls and each function call an endpoint with authorization control, if the user does not have all the permissions the call is going to fail
function getAllCatalogs() {
return $q.all([
$http.get(baseUrl + 'equipment-design/'),
$http.get(baseUrl + 'engines-design/'),
$http.get(baseUrl + 'suspension-design/'),
$http.get(baseUrl + 'artifacts-design/')
]).then(function (data) {
return data;
});
}
Due to $q.all needs that all promises finish satisfactorily if one of the previous calls fail due to the user does not have the permissions the whole call is going to fail, but maybe you are interested in when a call fail just return an empty array or a default object, so you have to handle this behavior for yourself, to achieved this you have to create a function which is the responsible to call the $http.get service and manage how the promise is going to be returned once the call fail or success, for this example let's call that function getCatalogPromise and in its parameters is going to be the URL for the service, the code will be:
function getCatalogPromise(url) {
var deferred = $q.defer();
$http.get(url).then(function (response) {
deferred.resolve(response)
}, function () {
deferred.resolve([]);
})
return deferred.promise;
}
function getAllCatalogs() {
return $q.all([
getCatalogPromise(baseUrl + 'equipment-design/'),
getCatalogPromise(baseUrl + 'engines-design/'),
getCatalogPromise(baseUrl + 'suspension-design/'),
getCatalogPromise(baseUrl + 'artifacts-design/')
]).then(function (data) {
return data;
});
}
if you pay attention to the code of getCatalogPromise it does'n matter what return the call to the service our deferred always is going to be in resolve state and that is what $q.all want, the only difference is that if the service fail we return an empty array.
Upvotes: 0
Reputation: 276326
The solution suggested by Interrobang is good (minus the bug) but if you dislike having decorators affect every single promise in your code you can get something similar to allSettled like this instead:
var suppress = function(x) { return x.catch(function(){}); }
$q.all(promises.map(suppress)).then(function(coords) {
for (var j = 0; j < coords.length ; j+=1) {
//coords[j] contains the coords on success or is undefined on failure
}
});
Upvotes: 9
Reputation: 17434
What you want is q.allSettled
, which isn't implemented in Angular.
Here's a relevant GitHub issue requesting its implementation.
And here is one possible implementation, called allComplete
, that you can add to your Angular app:
angular.module('App.services', ['ngResource'])
.config( function($provide) {
$provide.decorator("$q", ["$delegate", function($delegate) {
var $q = $delegate;
$q.allComplete = function(promises) {
if(!angular.isArray(promises)) {
throw Error("$q.allComplete only accepts an array.");
}
var deferred = $q.defer();
var passed = 0;
var failed = 0;
var responses = [];
angular.forEach(promises, function (promise, index) {
promise
.then( function(result) {
console.info('done', result);
passed++;
responses.push(result);
})
.catch( function(result) {
console.error('err', result);
failed++;
responses.push(result);
})
.finally( function() {
if((passed + failed) == promises.length) {
console.log("COMPLETE: " + "passed = " + passed + ", failed = " + failed);
if(failed > 0) {
deferred.reject(responses);
} else {
deferred.resolve(responses);
}
}
})
;
});
return deferred.promise;
};
return $q;
}]);
})
;
Upvotes: 0