Que fait $ .when.apply ($, someArray)?

110

Je lis des articles sur les différés et les promesses et je continue de paraître $.when.apply($, someArray). Je ne sais pas exactement ce que cela fait exactement, à la recherche d'une explication qu'une ligne fonctionne exactement (pas l'intégralité de l'extrait de code). Voici un peu de contexte:

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
la source
1
.done()peut être utilisé à la place de .thendans ce cas, juste FYI
Kevin B
2
fwiw, il y a un port différé pour souligner qui permet de passer un seul tableau afin _.whenque vous n'ayez pas besoin de l'utiliserapply
Eevee
En savoir plus sur .apply: developer.mozilla.org/en-US/docs/JavaScript/Reference/… .
Felix Kling
en relation : stackoverflow.com/questions/1986896/…
Felix Kling
1
L'article auquel l'OP se réfère dans sa première phrase a changé de lieu - il est maintenant à: flaviocopes.com/blog/deferreds-and-promises-in-javascript .
glaucon

Réponses:

161

.applyest utilisé pour appeler une fonction avec un tableau d'arguments. Il prend chaque élément du tableau et utilise chacun comme paramètre de la fonction. .applypeut également changer le contexte ( this) à l'intérieur d'une fonction.

Alors, prenons $.when. On a l'habitude de dire "quand toutes ces promesses sont résolues ... faites quelque chose". Il prend un nombre infini (variable) de paramètres.

Dans votre cas, vous avez un éventail de promesses; vous ne savez pas à combien de paramètres vous passez $.when. Passer le tableau lui-même $.whenne fonctionnerait pas, car il s'attend à ce que ses paramètres soient des promesses, pas un tableau.

C'est là .applyqu'intervient. Il prend le tableau, et appelle $.whenavec chaque élément comme paramètre (et s'assure que le thisest défini sur jQuery/ $), alors tout fonctionne :-)

Fusée Hazmat
la source
3
lorsque plusieurs promesses sont passées à la méthode $ .when. Dans quel ordre ils s'exécuteront? l'un après l'autre ou en parallèle?
Darshan
21
@Darshan: Vous ne "exécutez" pas de promesses. Vous attendez qu'ils soient résolus. Ils sont exécutés lors de leur création, $.whenattend juste qu'ils soient tous terminés avant de continuer.
Rocket Hazmat
1
qu'en est-il de la différence entre $.when($, arrayOfPromises).done(...) et $.when(null, arrayOfPromises).done(...) (que j'ai trouvé les deux comme solutions proposées dans les forums ...)
zeroquaranta
63

$ .when prend n'importe quel nombre de paramètres et se résout lorsque tous ceux-ci sont résolus.

anyFunction .apply (thisValue, arrayParameters) appelle la fonction anyFunction en définissant son contexte (thisValue sera le this dans cet appel de fonction) et transmet tous les objets de arrayParameters en tant que paramètres individuels.

Par exemple:

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

Est le même que:

$.when(def1, def2)

Mais la manière d'appeler apply vous permet de passer un tableau de nombre inconnu de paramètres. (Dans votre code, vous dites que vos données proviennent d'un service, alors c'est la seule façon d'appeler $ .when )

Pablo
la source
15

Ici, le code entièrement documenté.

// 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
la source
7
$.when.apply($, array)n'est pas la même chose que $.when(array). C'est la même chose que:$.when(array[0], array[1], ...)
Rocket Hazmat
1
C'est la raison principale pour laquelle est utilisé avec .apply , vous ne savez pas combien d'éléments processItemsDeferred a
Pablo
2

Malheureusement, je ne suis pas d'accord avec vous les gars.

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

Appellera everythingDonedès qu'un différé est rejeté , même s'il y a d'autres différés en attente .

Voici le script complet (je recommande 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');
}

C'est un bug? Je voudrais utiliser ceci comme le monsieur ci-dessus l'a décrit.

user3388213
la source
1
Le premier rejet déclenchera le toujours, mais pas le. Alors. Voir mon jsfiddle.net/logankd/s5dacgb3 que j'ai créé à partir de votre exemple. J'utilise JQuery 2.1.0 dans cet exemple.
Aligné le
1
C'est comme prévu. Il y a une tonne de cas où vous voudriez savoir dès que quelque chose échoue, n'attendez pas que tout se termine et vérifiez s'il y a eu des échecs. Surtout si le traitement ne peut pas continuer après un échec, pourquoi attendre que le reste se termine / échoue? Comme le suggère l'autre commentaire, vous pouvez utiliser la paire .then ou .fail & .done.
MPavlak
@GoneCoding Ce n'est pas utile. L'OP a demandé ce que fait appliquer () et vous avez suggéré une alternative terrible qui ne devrait jamais être utilisée :) c'est à quoi sert le bouton de vote vers le bas. Je ne l'ai pas non plus utilisé jusqu'à ce que vous refusiez de fournir POURQUOI vous l'avez fait et pourquoi (plus que votre préférence pour éviter les tableaux pour une raison quelconque)
MPavlak
@GoneCoding Merci d'avoir pris cette réponse
MPavlak
1
@GoneCoding lol, j'ai lu votre solution et fourni des commentaires. vous n'avez pas répondu à la question initiale. Vous ne pouviez pas expliquer pourquoi c'était comme ça. Ce sont des gens comme vous qui fournissent des solutions terribles aux gens qui apprennent. Vous avez clairement des compétences javascript limitées et vous me prenez pour le n00b. J'ai indiqué pourquoi c'était faux et vous ne pouviez même pas lire le code et dites-moi à la place que je me trompais. bon boulot mon pote!
MPavlak
1

Peut-être que quelqu'un peut trouver cela utile:

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

EverythingDone n'est pas appelé en cas de rejet

Vlado Kurelec
la source
0

Merci pour votre solution élégante:

var promise;

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

promise.then(everythingDone);

Juste un point: lorsque vous utilisez resolveWithpour obtenir certains paramètres, cela se rompt à cause de la promesse initiale définie sur indéfinie. Ce que j'ai fait pour que ça marche:

// 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
la source
2
Bien que cela fonctionne, ce n'est pas vraiment élégant. vous créez des promesses qui représentent la mise en œuvre différée de sorte que la dernière itération contienne "quand (workToDo [0..i-1], workToDo [i])" ou plus simplement "quand tout le travail précédent et ceci le travail est fait". Cela signifie que vous avez i + 1 lorsque vous enveloppez vos promesses. De plus, lorsque vous faites ce genre de choses, déballez simplement la première itération. var promise = processItem (données [0]); for (var i = 1; i <data.length; i ++) {promise = $ .when (promise, processItem (data [i])); }
MPavlak