Что делает $ .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в этом случае, просто к вашему сведению
Кевин Би
2
fwiw, есть отложенный порт для подчеркивания, который позволяет передавать один массив, _.whenпоэтому вам не нужно его использоватьapply
Eevee
Узнайте больше о .apply: developer.mozilla.org/en-US/docs/JavaScript/Reference/… .
Феликс Клинг
связанные : stackoverflow.com/questions/1986896/…
Феликс Клинг,
1
Статья, на которую ОП ссылается в своем первом предложении, была перемещена по адресу: flaviocopes.com/blog/deferreds-and-promises-in-javascript .
глаукон

Ответы:

161

.applyиспользуется для вызова функции с массивом аргументов. Он принимает каждый элемент массива и использует каждый в качестве параметра функции. .applyтакже может изменять context ( this) внутри функции.

Итак, берем $.when. Говорят, что «когда все эти обещания исполнятся ... сделай что-нибудь». Требуется бесконечное (переменное) количество параметров.

В вашем случае у вас есть множество обещаний; вы не знаете, сколько параметров вы передаете $.when. Передача самого массива в $.whenне сработает, потому что он ожидает, что его параметры будут обещаниями, а не массивом.

Вот где .applyприходит на помощь. Он принимает массив и вызывает $.whenкаждый элемент в качестве параметра (и проверяет, thisустановлено ли значение jQuery/ $), так что все работает :-)

Ракетный Хазмат
источник
3
когда в метод $ .when передается несколько обещаний. В каком порядке они будут исполнять? один за другим или параллельно?
Даршан
21
@Darshan: Вы не выполняете обещания. Вы ждете, пока они разрешатся. Они выполняются при создании, $.whenпросто ожидает, пока все они будут завершены, прежде чем продолжить.
Rocket Hazmat 02
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)

Но способ вызова apply позволяет вам передать массив с неизвестным количеством параметров. (В своем коде вы говорите, что данные поступают из службы, тогда это единственный способ вызвать $ .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
Пабло
2

К сожалению, я не могу согласиться с вами, ребята.

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

Будет вызывать, everythingDoneкак только один deferred будет отклонен , даже если есть другие deferred'ы, которые ожидают рассмотрения .

Вот полный сценарий (рекомендую 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');
}

Это ошибка? Я хотел бы использовать это так, как описал выше господин.

пользователь3388213
источник
1
При первом отклонении будет срабатывать всегда, но не .then. См. Мой jsfiddle.net/logankd/s5dacgb3, который я сделал из вашего примера. В этом примере я использую JQuery 2.1.0.
Согласовано
1
Это как задумано. Есть масса случаев, когда вы хотите знать, как только что- то выходит из строя, а не ждать, пока все завершится, и проверять, были ли сбои. Особенно, если обработка не может продолжаться после какого-либо сбоя, зачем ждать завершения / сбоя остальных? Как предполагалось в другом комментарии, вы можете использовать пару .then или .fail & .done.
MPavlak
@GoneCoding Это бесполезно. OP спросил, что делает apply (), и вы предложили ужасную альтернативу, которую никогда не следует использовать :) Это то, для чего предназначена кнопка голосования против. Я также не использовал его, пока вы не отказались сообщить, ПОЧЕМУ вы это сделали и почему (больше, чем вы предпочитаете по какой-то причине избегать массивов)
MPavlak
@GoneCoding Спасибо, что записали этот ответ
MPavlak
1
@GoneCoding lol, я прочитал ваше решение и оставил отзыв. вы не ответили на исходный вопрос. Вы не могли объяснить, почему это было именно так. Такие люди, как вы, предлагают ужасные решения людям, которые учатся. У вас явно ограниченные навыки работы с javascript, и вы принимаете меня за новичка. Я указал, почему это было неправильно, и вы даже не могли прочитать код, а вместо этого сказали мне, что я ошибаюсь. хороший друг!
MPavlak
1

Может быть, кому-то это пригодится:

$.when.apply($, processItemsDeferred).then(everythingDone).fail(noGood);

AllDone не вызывается в случае отклонения

Владо Курелец
источник
0

Только $ .when позволяет вызывать обратный вызов, когда все переданные ему обещания разрешаются / отклоняются. Обычно $ .when принимает переменное количество аргументов, использование .apply позволяет передать ему массив аргументов, это очень мощный инструмент. Для получения дополнительной информации о .apply: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/apply.

Роджер С
источник
0

Спасибо за элегантное решение:

var promise;

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

promise.then(everythingDone);

Только один момент: при использовании resolveWithдля получения некоторых параметров он прерывается из-за начального обещания, установленного на 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);
пользователь3544352
источник
2
Хотя это действительно работает, это не совсем элегантно. вы создаете обещания, которые представляют собой выполнение отложенного до i-го, так что последняя итерация содержит «when (workToDo [0..i-1], workToDo [i])» или более явно », когда вся предыдущая работа и это работа сделана". Это означает, что у вас есть i + 1, когда вы обертываете свои обещания. Кроме того, когда вы делаете подобные вещи, просто разверните первую итерацию. var обещание = processItem (данные [0]); для (var я = 1; я <data.length; я ++) {обещание = $ .when (обещание, processItem (данные [я])); }
MPavlak