$ .when.apply($,someArray)有什么作用?


110

我正在阅读有关递延和承诺的文章,并不断遇到$.when.apply($, someArray)。我不清楚这到底是做什么的,正在寻找一种解释,说明哪一可以正常工作(而不是整个代码段)。这里是一些上下文:

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');
}

1
.done()可以代替使用.then在这种情况下,只是FYI
凯文乙

2
首先,有一个下划线的Deferred端口可以传递单个数组,_.when因此您无需使用apply
Eevee 2013年



1
OP在其第一句话中提到的文章已移动位置-现在位于:flaviocopes.com/blog/deferreds-and-promises-in-javascript
glaucon

Answers:


161

.apply用于调用带有参数数组的函数。它接受数组中的每个元素,并将每个元素用作函数的参数。 .apply也可以this在函数内部更改context()。

所以,让我们来$.when。过去常说“当所有这些诺言都得到解决时……采取行动”。它需要无限(可变)数量的参数。

就您而言,您有各种各样的承诺;您不知道要传递给多少参数$.when。将数组本身传递给$.when是行不通的,因为它期望参数是promise,而不是数组。

就是这样.apply。它接收数组,并$.when以每个元素作为参数进行调用(并确保将this其设置为jQuery/ $),然后一切正常:-)


3
当多个承诺传递给$ .when方法时。他们将以什么顺序执行?一个接一个或另一个?
Darshan

21
@Darshan:您不会“兑现”承诺。您等待它们解决。它们在创建时$.when就执行,只是等待所有它们完成后再继续。
火箭Hazmat

1
$.when($, arrayOfPromises).done(...) 和 之间的区别是什么 $.when(null, arrayOfPromises).done(...) (我在论坛中都找到了建议的解决方案...)
zeroquaranta

63

$ .when接受任意数量的参数,并所有这些参数都解析后解析。

anyFunction .apply(thisValue,arrayParameters)调用函数anyFunction设置其上下文(thisValue将是该函数调用内的this),并将arrayParameters中的所有对象作为单独的参数传递。

例如:

$.when.apply($, [def1, def2])

是相同的:

$.when(def1, def2)

但是应用调用的方法允许您传递数量未知的数组。(在您的代码中,您说的是数据来自服务,这是调用$ .when的唯一方法)


15

在这里,代码已完整记录。

// 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');
}

7
$.when.apply($, array)一样的$.when(array)。它与以下内容相同:$.when(array[0], array[1], ...)
Rocket Hazmat

1
这就是为什么要与.apply一起使用的主要原因,您不知道processItemsDeferred具有很多元素
Pablo

2

不幸的是,我不能同意你们。

$.when.apply($, processItemsDeferred).always(everythingDone);

即使有其他待决的延期,也将everythingDone在一个延期被拒绝后立即呼叫。

继承人的完整脚本(我建议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');
}

这是一个错误吗?我想像上面的绅士一样使用它。


1
第一个拒绝将始终触发,但不会触发。请参阅我根据您的示例制作的jsfiddle.net/logankd/s5dacgb3。在此示例中,我使用的是JQuery 2.1.0。
2015年

1
这是预期的。在很多情况下,您想立即知道某件事失败,而不是等待所有事情完成并检查是否有失败。特别是如果在发生任何故障后处理无法继续进行,为什么还要等待其余的操作完成/失败?正如其他注释所建议的那样,您可以使用.then或.fail&.done对。
MPavlak '16

@GoneCoding没有用。OP询问了apply()的作用,并且您建议了一个绝不应该使用的可怕替代方法:)这是向下表决按钮的作用。在您拒绝提供原因时,我也没有使用它,为什么这样做(出于某种原因,您更希望避免使用数组)
MPavlak,2016年

@GoneCoding感谢您记下这个答案
MPavlak '16

1
@GoneCoding大声笑,我阅读了您的解决方案并提供了反馈。您没有提供原始问题的答案。您无法解释为什么会这样。像您这样的人为正在学习的人提供了糟糕的解决方案。您显然具有有限的JavaScript技能,并把我当成了n00b。我指出了为什么会出错,而您甚至无法阅读代码,而是告诉我我错了。好伙伴!
MPavlak '16



0

感谢您的优雅解决方案:

var promise;

for(var i = 0; i < data.length; i++){
  promise = $.when(promise, processItem(data[i]));
}

promise.then(everythingDone);

一点:当resolveWith用于获取一些参数时,由于初始promise设置为undefined,所以它会中断。我做了什么使它工作:

// 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);

2
尽管确实可以,但它并不是很优雅。您创建的promise表示要进行的最新递延操作,以便最后一次迭代包含“ when(workToDo [0..i-1],workToDo [i])”或更明确地“当所有先前的工作和工作已经完成”。这意味着当您兑现承诺时,您拥有i +1。同样,在进行此类操作时,只需解开第一个迭代即可。var promise = processItem(data [0]); for(var i = 1; i <data.length; i ++){promise = $ .when(promise,processItem(data [i]));; }
MPavlak '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.