I am looking for a way to do a callback after two ajax calls completes:
$.when(
call1(),
call2()
).always(function() {
// Here I want to be sure the two calls are done and to get their responses
);
The catch is that one of the calls might fail. So, in my code the always will invoked without waiting to the other call.
How can I wait for both calls to done (success or failure)?
I am looking for a way to do a callback after two ajax calls completes:
$.when(
call1(),
call2()
).always(function() {
// Here I want to be sure the two calls are done and to get their responses
);
The catch is that one of the calls might fail. So, in my code the always will invoked without waiting to the other call.
How can I wait for both calls to done (success or failure)?
Share Improve this question asked Feb 26, 2013 at 15:37 NaorNaor 24.1k50 gold badges155 silver badges270 bronze badges 5 |4 Answers
Reset to default 12Here is something that should do the trick:
$.whenAllDone = function() {
var deferreds = [];
var result = $.Deferred();
$.each(arguments, function(i, current) {
var currentDeferred = $.Deferred();
current.then(function() {
currentDeferred.resolve(false, arguments);
}, function() {
currentDeferred.resolve(true, arguments);
});
deferreds.push(currentDeferred);
});
$.when.apply($, deferreds).then(function() {
var failures = [];
var successes = [];
$.each(arguments, function(i, args) {
// If we resolved with `true` as the first parameter
// we have a failure, a success otherwise
var target = args[0] ? failures : successes;
var data = args[1];
// Push either all arguments or the only one
target.push(data.length === 1 ? data[0] : args);
});
if(failures.length) {
return result.reject.apply(result, failures);
}
return result.resolve.apply(result, successes);
});
return result;
}
Check out this Fiddle to see how it works.
Basically it waits for all Deferreds to finish no matter if they fail or not and collects all the results. If we have failures, the returned Deferred will fail with a list of all failures and resolve with all successes otherwise.
It isn't pretty, but you could have a global "completed" variable for each ajax call to set when complete. Each call would also check whether both variables were set, and if so, call your always function.
You can also nest the calls:
$.when(call1()).always(function(){
$.when(call2()).always(function(){
// Here I want to be sure the two calls are done and to get their responses
});
});
But of course the two calls will become synchronous to each other.
Daff's answer is good. There is only one problem. When there is only one deferred, things don't work.
The problem was inside jquery's when
method.
jquery.when: function( subordinate /* , ..., subordinateN */ ) { ...
It has a line like:
// If resolveValues consist of only a single Deferred, just use that.
deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
And this changes the shape of the arguments, so I had to put it back to the common shape my code expects (i.e. the same shape when multiple deferreds are passed to whenAllDone
)
const jqueryWhenUsesSubordinate = deferreds.length == 1;
const deferredArgs = jqueryWhenUsesSubordinate
? [[ arguments[ 0 ], arguments[ 1 ] ]]
: arguments
$.each(deferredArgs, function (i, resolvedArgs) {
var target = !resolvedArgs[0] ? failures : successes;
var data = resolvedArgs[1];
target.push(data.length === 1 ? data[0] : data);
});
Additionally, I changed the function signature to match more closely to Promise.allSettled
in that it should take an array parameter of deferred objects, then instead of looping over arguments
to set up the deferreds
array, you loop over that parameter passed in.
This allows you to programmatically create a variable length of deferreds into an array and pass that into whenAllDone
.
$.when
already returns a promise object, i.e. the OP already uses promises. – Felix Kling Commented Feb 26, 2013 at 15:48$.when
, which only rejects and resolves once all promises are rejected/resolved. Here is how$.when
is implemented: github.com/jquery/jquery/blob/1.9.1/src/deferred.js#L91. – Felix Kling Commented Feb 26, 2013 at 15:55