Co robi $ .when.apply ($, someArray)?

110

Ja czytając o Deferreds i obiecuje i ciągle napotykając $.when.apply($, someArray). Nie jestem pewien, co to dokładnie robi, szukając wyjaśnienia, że jedna linia działa dokładnie (a nie cały fragment kodu). Oto kontekst:

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');
}
manafire
źródło
1
.done()może być używany zamiast .thenw tym przypadku, po prostu FYI
Kevin B
2
fwiw, istnieje port odroczony do podkreślenia, który umożliwia przekazanie pojedynczej tablicy, _.whenwięc nie musisz jej używaćapply
Eevee,
Dowiedz się więcej o .apply: developer.mozilla.org/en-US/docs/JavaScript/Reference/… .
Felix Kling,
1
Artykuł, do którego OP odwołuje się w swoim pierwszym zdaniu, zmienił lokalizacje - jest teraz pod adresem: flaviocopes.com/blog/deferreds-and-promises-in-javascript .
glaucon

Odpowiedzi:

161

.applysłuży do wywołania funkcji z tablicą argumentów. Pobiera każdy element tablicy i używa każdego jako parametru funkcji. .applymoże również zmienić context ( this) wewnątrz funkcji.

Więc weźmy $.when. Zwykło się mówić „kiedy wszystkie te obietnice zostaną spełnione ... zrób coś”. Zajmuje nieskończoną (zmienną) liczbę parametrów.

W twoim przypadku masz szereg obietnic; nie wiesz, do ilu parametrów przekazujesz $.when. Przekazanie samej tablicy do $.whennie zadziała, ponieważ oczekuje, że jej parametry będą obietnicami, a nie tablicą.

I tu .applypojawia się. Pobiera tablicę i wywołuje $.whenkażdy element jako parametr (i upewnia się, że thisjest ustawiony na jQuery/ $), więc wszystko działa :-)

Rocket Hazmat
źródło
3
kiedy wiele obietnic jest przekazywanych do metody $ .when. W jakiej kolejności wykonają? jeden po drugim czy równolegle?
Darśan
21
@Darshan: Nie obiecujesz "uciekać". Czekasz, aż zostaną rozwiązane. Są wykonywane po utworzeniu, $.whenpo prostu czeka, aż wszystkie zostaną ukończone, zanim przejdą dalej.
Rocket Hazmat
1
a co z różnicą między $.when($, arrayOfPromises).done(...) i $.when(null, arrayOfPromises).done(...) (które znalazłem oba jako proponowane rozwiązania na forach ...)
zeroquaranta
63

$ .when przyjmuje dowolną liczbę parametrów i rozwiązuje problem, gdy wszystkie z nich zostaną rozwiązane.

anyFunction .apply (thisValue, arrayParameters) wywołuje funkcję anyFunction ustawiając jej kontekst (thisValue będzie tym wywołaniem tej funkcji) i przekazuje wszystkie obiekty w arrayParameters jako indywidualne parametry.

Na przykład:

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

Jest taki sam jak:

$.when(def1, def2)

Jednak zastosowany sposób wywołania umożliwia przekazanie tablicy o nieznanej liczbie parametrów. (W swoim kodzie mówisz, że dane pochodzą z usługi, to jedyny sposób wywołania $., Gdy )

Pablo
źródło
15

Tutaj kod w pełni udokumentowany.

// 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');
}
Yanick Rochon
źródło
7
$.when.apply($, array)to nie to samo co $.when(array). To to samo, co:$.when(array[0], array[1], ...)
Rocket Hazmat,
1
To jest główny powód, dla którego jest używany z .apply , nie wiesz, ile elementów processItemsDeferred ma
Pablo
2

Niestety nie mogę się z wami zgodzić.

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

Zadzwoni, everythingDonegdy tylko jeden odroczony zostanie odrzucony , nawet jeśli są inne oczekujące na odroczenie .

Oto pełny skrypt (polecam 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');
}

Czy to błąd? Chciałbym tego używać tak, jak dżentelmen powyżej opisał.

user3388213
źródło
1
Pierwsza odrzucenie spowoduje uruchomienie zawsze, ale nie. Zobacz mój jsfiddle.net/logankd/s5dacgb3, który utworzyłem na podstawie twojego przykładu. W tym przykładzie używam JQuery 2.1.0.
Wyrównany
1
Jest to zgodne z przeznaczeniem. Jest mnóstwo przypadków, w których chciałbyś wiedzieć, gdy tylko coś się nie powiedzie, a nie czekać, aż wszystko się zakończy i sprawdzić, czy wystąpiły awarie. Zwłaszcza jeśli przetwarzanie nie może być kontynuowane po jakiejkolwiek awarii, po co czekać, aż reszta się zakończy / zakończy się niepowodzeniem? Jak sugerował inny komentarz, możesz użyć pary .then lub .fail & .done.
MPavlak
@GoneCoding To nie jest przydatne. OP zapytał o to, co robi Apply (), a ty zasugerowałeś okropną alternatywę, której nigdy nie powinno się używać :) do tego służy przycisk głosowania w dół. Nie użyłem go również, dopóki nie odmówiłeś podania DLACZEGO to zrobiłeś, dlaczego (z jakiegoś powodu bardziej niż twoje preferencje, aby unikać tablic)
MPavlak
@GoneCoding Dziękujemy za usunięcie tej odpowiedzi
MPavlak
1
@GoneCoding lol, przeczytałem twoje rozwiązanie i przekazałem opinię. nie udzieliłeś odpowiedzi na pierwotne pytanie. Nie można było wyjaśnić, dlaczego było tak, jak było. To ludzie tacy jak Ty zapewniają straszne rozwiązania ludziom, którzy się uczą. Wyraźnie masz ograniczone umiejętności javascript i traktujesz mnie jako n00b. Wskazałem, dlaczego jest źle, a ty nie mogłeś nawet odczytać kodu, a zamiast tego powiedz mi, że się mylę. dobra robota kolego!
MPavlak
1

Może ktoś uzna to za przydatne:

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

EverythingDone nie jest wywoływane w przypadku odrzucenia

Vlado Kurelec
źródło
0

Dzięki za eleganckie rozwiązanie:

var promise;

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

promise.then(everythingDone);

Tylko jeden punkt: podczas używania resolveWithdo uzyskania niektórych parametrów, zrywa się z powodu początkowej obietnicy ustawionej na niezdefiniowaną. Co zrobiłem, żeby to zadziałało:

// 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);
user3544352
źródło
2
Chociaż to działa, nie jest zbyt eleganckie. tworzysz obietnice, które reprezentują ostatnie odroczone wykonanie, tak aby ostatnia iteracja zawierała „when (workToDo [0..i-1], workToDo [i])” lub bardziej wyraźnie „kiedy wszystkie poprzednie prace i to praca jest wykonana ”. Oznacza to, że masz i + 1, gdy zawijasz swoje obietnice. Ponadto, wykonując tego typu czynności, po prostu rozpakuj pierwszą iterację. var promise = processItem (data [0]); for (var i = 1; i <data.length; i ++) {promise = $ .when (promise, processItem (data [i])); }
MPavlak