Reputation: 6363
I have a function which makes web service calls to my server and returns an array of promises.
However some of these calls might work while others might not. The way my function is currently set up, if one of them fails, it alerts that the entire thing failed. If I'm making 5 calls, 1 might fail. I need to log this correctly and I'm not sure how to do it.
An ideal response/log would be:
Right now the whole thing will return "The handle user operation failed" because call 4 failed.
Function:
var manageGroup = function (add, group, users){
var deffered = $q.defer();
var arrPromises = [];
var promiseIndex = arrPromises.length;
var usersLength = users.length;
var operation = add ? "AddUserToGroup" : "RemoveUserFromGroup";
var actionText = add ? "Added: " : "Removed: "
var actionText2 = add ? " to " : " from "
//Apply operation on selected groups
for (var i = 0; i < usersLength; i++){
arrPromises[i] = $().SPServices({
operation: operation,
groupName: group.name,
userLoginName: users[i].domain
});
}
$q.all(arrPromises).then(
function (){
//when promises are finsihed
for (var i = 0; i < usersLength; i++){
console.log(actionText + users[i].name + actionText2 + group.name);
};
deffered.resolve();
},
//function incase of AJAX failure
function (){
alert('The handle user operation failed.');
}
)
return deffered.promise;
}
I tried to handle the promises individually instead of using the $q.all but now I'm not getting anything in the log:
I took this section out:
/*$q.all(arrPromises).then(
function (){
//when promises are finsihed
for (var i = 0; i < usersLength; i++){
console.log(actionText + users[i].name + actionText2 + group.name);
};
deferred.resolve();
},
//function incase of AJAX failure
function (){
alert('The handle user operation failed.');
}
) */
Introduced this instead:
for (var i = 0; i<promiseIndex; i++){
arrPromises[i].then(
function (){
console.log(actionText + user[i].name + actionText2 + group.name);
}
),
function (){
alert('Failed to add/remove'+ user[i].name + ' to ' + group.name)
}
}
$q.all(arrPromises).then(function (){
deferred.resolve();
}, function (){
deferred.reject();
})
Upvotes: 3
Views: 3346
Reputation: 665040
I tried to handle the promises individually instead of using the $q.all but now I'm not getting anything in the log
You seem to have fallen for the classical closure-in-a-loop problem, with your i
variable having the wrong value when the callback is executed. Instead, use this:
for (var i = 0; i<promiseIndex; i++) (function(i) {
arrPromises[i].then(function() {
console.log(actionText + user[i].name + actionText2 + group.name);
}, function( ){
alert('Failed to add/remove'+ user[i].name + ' to ' + group.name)
});
})(i);
Also you had mismatched parenthesis in the then
call, essentially not passing the error handler.
Now, each promise is handled individually; yet their order is not persisted. For that, you will need to use some kind of all
, have a look at @Florian's answer.
Also notice that there's no reason to use that deffered
explicitly. Just return $q.all(arrPromises)
! Manually resolving deferreds and returning their promise is cumbersome and error-prone - in your original code you simply forgot to reject it in case of an error. Don't use this when you already have promises and can use combinators on them.
Upvotes: 1
Reputation: 43785
It seems that you're having an issue with one ajax call failure causing an overall failure of all()
. You could just catch the failure of the individual AJAX calls and resolve the corresponding promise with a value of your choosing. Here, I am just using an empty string.
Keep in mind, this is just sample code to demonstrate the point.
//store promises to use for all()
var promises = [];
//loop for ajax calls
for (var i=0; i<3; ++i) {
//new deferred for each call
var deferred = $q.defer();
//cache the promise
promises[i] = deferred.promise;
//use another function to avoid unwanted variable increment
makeCall(deferred, i);
}
$q.all(promises).then(function(allData) {
console.log(allData);
});
function makeCall(deferred, i) {
//make the ajax call
$http.get('file'+i+'.txt').then(function(resp) {
console.log('Call '+i+' returned.');
//resolve the promise with ajax data if successful
deferred.resolve(resp.data);
}, function() {
//resolve with something else on failure
deferred.resolve('');
});
}
Upvotes: 4
Reputation: 60787
Q (on which ng.$q is based on) or bluebird have a method fulfilling exactly your needs.
For bluebird, you'd go this way:
var Promise = require('bluebird');
Promise.settle(arrPromises).then(function(promises) {
promises.forEach(function(p) {
if (promise.isRejected()) {
// it's a rejected promise.
}
else {
// it's a resolved promise.
}
});
});
And for Q, you'd go this way:
var Q = require('q');
Q.allSettled(arrPromises).then(function(promises) {
promises.forEach(function(p) {
if (p.state === 'fulfilled') {
// it's a resolved promise.
}
else {
// it's a rejected promise.
}
});
});
The nice thing about both these libraries is that they're compliant with the Promises/A+ specification. Which means you can take off ng.$q, put one of these, and your current code will still work.
Upvotes: 5