Was macht $ .when.apply ($, someArray)?

110

Ich lese über Aufgeschobene und Versprechen und stoße immer wieder darauf $.when.apply($, someArray). Ich bin mir ein wenig unklar, was dies genau bewirkt, und suche nach einer Erklärung, dass eine Zeile genau funktioniert (nicht das gesamte Code-Snippet). Hier ist ein Zusammenhang:

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
quelle
1
.done()kann anstelle von .thenin diesem Fall nur FYI
Kevin B
2
fwiw, es gibt einen verzögerten Port zum Unterstreichen, der es ermöglicht, ein einzelnes Array an zu übergeben, _.whendamit Sie es nicht verwenden müssenapply
Eevee
Erfahren Sie mehr über .apply: developer.mozilla.org/en-US/docs/JavaScript/Reference/… .
Felix Kling
Verwandte : stackoverflow.com/questions/1986896/…
Felix Kling
1
Der Artikel, auf den sich das OP in seinem ersten Satz bezieht, hat Standorte verschoben - er befindet sich jetzt unter: flaviocopes.com/blog/deferreds-and-promises-in-javascript .
Glaukon

Antworten:

161

.applywird verwendet, um eine Funktion mit einem Array von Argumenten aufzurufen. Es nimmt jedes Element im Array und verwendet jedes als Parameter für die Funktion. .applykann auch den Kontext ( this) innerhalb einer Funktion ändern .

Also, lass uns nehmen $.when. Es wird verwendet, um zu sagen "wenn all diese Versprechen gelöst sind ... etwas tun". Es dauert eine unendliche (variable) Anzahl von Parametern.

In Ihrem Fall haben Sie eine Reihe von Versprechungen; Sie wissen nicht, an wie viele Parameter Sie übergeben $.when. Das Array selbst an zu übergeben $.whenwürde nicht funktionieren, da erwartet wird, dass seine Parameter Versprechen sind, kein Array.

Das ist , wo .applykommt in . Es das Array nimmt, und Anrufe $.whenmit jedem Element als Parameter (und stellt sicher , dass die thiseingestellt ist jQuery/ $), es so ist, dann alles funktioniert :-)

Rakete Hazmat
quelle
3
wenn mehrere Versprechen an die Methode $ .when übergeben werden. In welcher Reihenfolge werden sie ausgeführt? nacheinander oder parallel?
Darshan
21
@Darshan: Du "rennst" keine Versprechen. Sie warten darauf, dass sie gelöst werden. Sie werden beim $.whenErstellen ausgeführt und warten nur, bis alle fertig sind, bevor Sie fortfahren.
Rocket Hazmat
1
Was ist mit dem Unterschied zwischen $.when($, arrayOfPromises).done(...) und $.when(null, arrayOfPromises).done(...) (die ich beide als Lösungsvorschläge in den Foren gefunden habe ...)
zeroquaranta
62

$ .when nimmt eine beliebige Anzahl von Parametern an und wird aufgelöst, wenn alle diese aufgelöst wurden.

anyFunction .apply (thisValue, arrayParameters) ruft die Funktion anyFunction auf, die ihren Kontext festlegt (thisValue ist dies innerhalb dieses Funktionsaufrufs) und übergibt alle Objekte in arrayParameters als einzelne Parameter.

Beispielsweise:

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

Ist das gleiche wie:

$.when(def1, def2)

Mit der Apply- Aufrufmethode können Sie jedoch ein Array mit einer unbekannten Anzahl von Parametern übergeben. (In Ihrem Code sagen Sie, dass Ihre Daten von einem Dienst stammen, dann ist dies die einzige Möglichkeit, $ .when aufzurufen. )

Pablo
quelle
15

Hier ist der Code vollständig dokumentiert.

// 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
quelle
7
$.when.apply($, array)ist nicht dasselbe wie $.when(array). Es ist das gleiche wie:$.when(array[0], array[1], ...)
Rocket - Hazmat
1
Das ist der Hauptgrund, warum mit .apply verwendet wird , Sie wissen nicht, wie viele Elemente processItemsDeferred hat
Pablo
2

Leider kann ich euch nicht zustimmen.

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

Wird anrufen everythingDone, sobald ein Aufgeschobener abgelehnt wird , auch wenn andere aufgeschobene Rückstände ausstehen .

Hier ist das vollständige Skript (ich empfehle 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');
}

Ist das ein Fehler? Ich würde dies gerne so verwenden, wie es der Herr oben beschrieben hat.

user3388213
quelle
1
Die erste Zurückweisung wird die immer, aber nicht die .then feuern. Siehe mein jsfiddle.net/logankd/s5dacgb3 , das ich aus Ihrem Beispiel gemacht habe. In diesem Beispiel verwende ich JQuery 2.1.0.
Ausgerichtet am
1
Dies ist wie beabsichtigt. Es gibt eine Menge Fälle, in denen Sie wissen möchten, sobald etwas fehlschlägt. Warten Sie nicht, bis alles abgeschlossen ist, und prüfen Sie, ob Fehler aufgetreten sind. Insbesondere wenn die Verarbeitung nach einem Fehler nicht fortgesetzt werden kann, warum warten, bis der Rest abgeschlossen ist / fehlschlägt? Wie im anderen Kommentar vorgeschlagen, können Sie das Paar .then oder .fail & .done verwenden.
MPavlak
@GoneCoding Es ist nicht nützlich. Das OP fragte nach den Funktionen von apply () und Sie schlugen eine schreckliche Alternative vor, die niemals verwendet werden sollte :) Dafür dient der Down Vote-Button. Ich habe es auch nicht benutzt, bis Sie sich geweigert haben
anzugeben, WARUM
@GoneCoding Vielen Dank, dass Sie diese Antwort
notiert
1
@GoneCoding lol, ich habe Ihre Lösung gelesen und Feedback gegeben. Sie haben keine Antwort auf die ursprüngliche Frage gegeben. Man konnte nicht erklären, warum es so war, wie es war. Es sind Menschen wie Sie, die Menschen, die lernen, schreckliche Lösungen bieten. Sie haben eindeutig begrenzte Javascript-Kenntnisse und nehmen mich als n00b. Ich gab an, warum es falsch war und Sie konnten nicht einmal den Code lesen und mir stattdessen sagen, dass ich falsch liege. gute Arbeit Kumpel!
MPavlak
1

Vielleicht kann jemand dies nützlich finden:

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

EverythingDone wird bei Ablehnung nicht aufgerufen

Vlado Kurelec
quelle
0

$ .wenn es allein möglich ist, einen Rückruf aufzurufen, wenn alle an ihn weitergegebenen Versprechen gelöst / abgelehnt werden. Normalerweise ist $ .wenn eine variable Anzahl von Argumenten verwendet wird. Wenn Sie .apply verwenden, können Sie ein Array von Argumenten übergeben. Dies ist sehr leistungsfähig. Weitere Informationen zu .apply: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/apply

Roger C.
quelle
0

Vielen Dank für Ihre elegante Lösung:

var promise;

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

promise.then(everythingDone);

Nur ein Punkt: Wenn resolveWithSie einige Parameter abrufen, wird dies aufgrund des anfänglichen Versprechens, das auf undefiniert festgelegt ist, unterbrochen. Was ich getan habe, damit es funktioniert:

// 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
quelle
2
Das funktioniert zwar, ist aber nicht wirklich elegant. Sie erstellen Versprechungen, die die bis zu i-ten verzögerten Aktionen darstellen, sodass die letzte Iteration "when (workToDo [0..i-1], workToDo [i])" oder klarer "when all previous work and this" enthält Arbeit ist erledigt". Dies bedeutet, dass Sie i + 1 haben, wenn Sie Ihre Versprechen einpacken. Wenn Sie so etwas tun, packen Sie einfach die erste Iteration aus. var versprechen = processItem (data [0]); für (var i = 1; i <Datenlänge; i ++) {Versprechen = $ .when (Versprechen, Prozesselement (Daten [i])); }
MPavlak