What does $.when.apply($, someArray) do?
JavascriptJqueryAsynchronousPromiseJavascript Problem Overview
I'm reading about Deferreds and Promises and keep coming across $.when.apply($, someArray)
. I'm a little unclear on what this does exactly, looking for an explanation that one line works exactly (not the entire code snippet). Here's some context:
var data = [1,2,3,4]; // the ids coming back from serviceA
var processItemsDeferred = [];
for(var i = 0; i < data.length; i++){
processItemsDeferred.push(processItem(data[i]));
}
$.when.apply($, processItemsDeferred).then(everythingDone);
function processItem(data) {
var dfd = $.Deferred();
console.log('called processItem');
//in the real world, this would probably make an AJAX call.
setTimeout(function() { dfd.resolve() }, 2000);
return dfd.promise();
}
function everythingDone(){
console.log('processed all items');
}
Javascript Solutions
Solution 1 - Javascript
.apply
is used to call a function with an array of arguments. It takes each element in the array, and uses each as a parameter to the function. .apply
can also change the context (this
) inside a function.
So, let's take $.when
. It's used to say "when all these promises are resolved... do something". It takes an infinite (variable) number of parameters.
In your case, you have an array of promises; you don't know how many parameters you're passing to $.when
. Passing the array itself to $.when
wouldn't work, because it expects its parameters to be promises, not an array.
That's where .apply
comes in. It takes the array, and calls $.when
with each element as a parameter (and makes sure the this
is set to jQuery
/$
), so then it all works :-)
Solution 2 - Javascript
$.when takes any number of parameters and resolves when all of these have resolved.
anyFunction.apply(thisValue, arrayParameters) calls the function anyFunction setting its context (thisValue will be the this within that function call) and passes all the objects in arrayParameters as individual parameters.
For example:
$.when.apply($, [def1, def2])
Is the same as:
$.when(def1, def2)
But the apply way of calling allows you to pass an array of unknown number of parameters. (In your code, you are saying that you data comes from a service, then that is the only way to call $.when)
Solution 3 - Javascript
Here, the code fully documented.
// 1. Declare an array of 4 elements
var data = [1,2,3,4]; // the ids coming back from serviceA
// 2. Declare an array of Deferred objects
var processItemsDeferred = [];
// 3. For each element of data, create a Deferred push push it to the array
for(var i = 0; i < data.length; i++){
processItemsDeferred.push(processItem(data[i]));
}
// 4. WHEN ALL Deferred objects in the array are resolved THEN call the function
// Note : same as $.when(processItemsDeferred[0], processItemsDeferred[1], ...).then(everythingDone);
$.when.apply($, processItemsDeferred).then(everythingDone);
// 3.1. Function called by the loop to create a Deferred object (data is numeric)
function processItem(data) {
// 3.1.1. Create the Deferred object and output some debug
var dfd = $.Deferred();
console.log('called processItem');
// 3.1.2. After some timeout, resolve the current Deferred
//in the real world, this would probably make an AJAX call.
setTimeout(function() { dfd.resolve() }, 2000);
// 3.1.3. Return that Deferred (to be inserted into the array)
return dfd.promise();
}
// 4.1. Function called when all deferred are resolved
function everythingDone(){
// 4.1.1. Do some debug trace
console.log('processed all items');
}
Solution 4 - Javascript
Unfortunately I can not agree with you guys.
$.when.apply($, processItemsDeferred).always(everythingDone);
Will call everythingDone
as soon as one deferred gets rejected, even if there are other deferreds that are pending.
Heres the full script (I recommend http://jsfiddle.net/):
var data = [1,2,3,4]; // the ids coming back from serviceA
var processItemsDeferred = [];
for(var i = 0; i < data.length; i++){
processItemsDeferred.push(processItem(data[i]));
}
processItemsDeferred.push($.Deferred().reject());
//processItemsDeferred.push($.Deferred().resolve());
$.when.apply($, processItemsDeferred).always(everythingDone);
function processItem(data) {
var dfd = $.Deferred();
console.log('called processItem');
//in the real world, this would probably make an AJAX call.
setTimeout(function() { dfd.resolve(); }, 2000);
return dfd.promise();
}
function everythingDone(){
alert('processed all items');
}
It this a bug? I would like to use this like the gentleman above described it.
Solution 5 - Javascript
Maybe someone can find this useful:
$.when.apply($, processItemsDeferred).then(everythingDone).fail(noGood);
everythingDone isn't called in case of any reject
Solution 6 - Javascript
$.when alone makes it possible for a callback to be called when every promises passed to it are resolved/rejected. Normally, $.when takes a variable number of arguments, using .apply makes it possible to pass it an array of arguments, it's very powerful. For more info on .apply: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/apply
Solution 7 - Javascript
Thanks for your elegant solution:
var promise;
for(var i = 0; i < data.length; i++){
promise = $.when(promise, processItem(data[i]));
}
promise.then(everythingDone);
Just one point: When using resolveWith
to get some parameters, it breaks because of the initial promise set to undefined. What i did to make it work:
// Start with an empty resolved promise - undefined does the same thing!
var promise;
for(var i = 0; i < data.length; i++){
if(i==0) promise = processItem(data[i]);
else promise = $.when(promise, processItem(data[i]));
}
promise.then(everythingDone);