¿Qué hace $ .when.apply ($, someArray)?

110

Estoy leyendo sobre diferidos y promesas y sigo encontrándome $.when.apply($, someArray). No tengo claro qué hace esto exactamente, buscando una explicación de que una línea funciona exactamente (no todo el fragmento de código). Aquí hay algo de contexto:

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
fuente
1
.done()se puede usar en lugar de .thenen este caso, solo para su información
Kevin B
2
fwiw, hay un puerto diferido para subrayar que permite pasar una sola matriz para _.whenque no necesite usarloapply
Eevee
Obtenga más información sobre .apply: developer.mozilla.org/en-US/docs/JavaScript/Reference/… .
Felix Kling
relacionado : stackoverflow.com/questions/1986896/…
Felix Kling
1
El artículo al que se refiere el OP en su primera oración ha cambiado de ubicación, ahora está en: flaviocopes.com/blog/deferreds-and-promises-in-javascript .
glaucon

Respuestas:

161

.applyse usa para llamar a una función con una matriz de argumentos. Toma cada elemento de la matriz y usa cada uno como parámetro de la función. .applytambién puede cambiar el contexto ( this) dentro de una función.

Entonces, tomemos $.when. Se usa para decir "cuando todas estas promesas se resuelvan ... haz algo". Requiere un número infinito (variable) de parámetros.

En su caso, tiene una serie de promesas; no sabe a cuántos parámetros está pasando $.when. Pasar la matriz en sí a $.whenno funcionaría, porque espera que sus parámetros sean promesas, no una matriz.

Ahí es donde .applyentra en juego . Toma la matriz y llama $.whencon cada elemento como parámetro (y se asegura de que thisesté configurado en jQuery/ $), entonces todo funciona :-)

Cohete Hazmat
fuente
3
cuando se pasan varias promesas al método $ .when. ¿En qué orden se ejecutarán? uno tras otro o en paralelo?
Darshan
21
@Darshan: No "ejecutas" las promesas. Esperas a que se resuelvan. Se ejecutan cuando se crean, $.whensolo espera a que finalicen todos antes de continuar.
Rocket Hazmat
1
¿qué pasa con la diferencia entre $.when($, arrayOfPromises).done(...) y $.when(null, arrayOfPromises).done(...) (que encontré como soluciones propuestas en los foros ...)
zeroquaranta
63

$ .when toma cualquier número de parámetros y se resuelve cuando todos se han resuelto.

anyFunction .apply (thisValue, arrayParameters) llama a la función anyFunction configurando su contexto (thisValue será el this dentro de esa llamada de función) y pasa todos los objetos en arrayParameters como parámetros individuales.

Por ejemplo:

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

Es lo mismo que:

$.when(def1, def2)

Pero la aplican forma de llamar le permite pasar una matriz de número desconocido de parámetros. (En su código, está diciendo que sus datos provienen de un servicio, entonces esa es la única forma de llamar a $ .when )

Pablo
fuente
15

Aquí, el código completamente documentado.

// 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
fuente
7
$.when.apply($, array)no es lo mismo que $.when(array). Es lo mismo que:$.when(array[0], array[1], ...)
Rocket Hazmat
1
Esa es la razón principal por la que se usa con .apply , no sabes cuántos elementos tiene processItemsDeferred
Pablo
2

Lamentablemente no puedo estar de acuerdo con ustedes.

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

Llamará everythingDonetan pronto como se rechace uno de los aplazados , incluso si hay otros aplazados pendientes .

Aquí está el script completo (recomiendo 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');
}

¿Es esto un error? Me gustaría usar esto como lo describió el caballero arriba.

usuario3388213
fuente
1
El primer rechazo disparará el siempre, pero no el .entonces. Vea mi jsfiddle.net/logankd/s5dacgb3 que hice a partir de su ejemplo. Estoy usando JQuery 2.1.0 en este ejemplo.
Alineado el
1
Esto es como se pretendía. Hay un montón de casos en los que querría saber tan pronto como algo falla, no esperar a que todo se complete y verificar si hubo fallas. Especialmente si el procesamiento no puede continuar después de cualquier falla, ¿por qué esperar a que el resto termine / falle? Como sugirió el otro comentario, puede usar .then o .fail & .done pair.
MPavlak
@GoneCoding No es útil. El OP preguntó qué hace apply () y usted sugirió una alternativa terrible que nunca debería usarse :) para eso ES el botón de voto negativo. Tampoco lo
usé
@GoneCoding Gracias por anotar esa respuesta
MPavlak
1
@GoneCoding lol, leí tu solución y proporcioné comentarios. no proporcionó una respuesta a la pregunta original. No se pudo explicar por qué era como era. Son personas como usted las que brindan soluciones terribles a las personas que están aprendiendo. Claramente tienes una habilidad limitada de JavaScript y me estás tomando como el n00b. Indiqué por qué estaba mal y ni siquiera pudiste leer el código y en cambio me dijiste que estaba equivocado. buen trabajo amigo!
MPavlak
1

Quizás alguien pueda encontrar esto útil:

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

EverythingDone no se llama en caso de rechazo

Vlado Kurelec
fuente
0

Gracias por tu elegante solución:

var promise;

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

promise.then(everythingDone);

Solo un punto: cuando se usa resolveWithpara obtener algunos parámetros, se rompe debido a la promesa inicial establecida en indefinida. Lo que hice para que funcionara:

// 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);
usuario3544352
fuente
2
Si bien eso funciona, no es realmente elegante. estás creando promesas que representan el aplazamiento hasta el i-ésimo hecho de modo que la última iteración contenga "when (workToDo [0..i-1], workToDo [i])" o más claramente "cuando todo el trabajo anterior y este El trabajo está hecho". Esto significa que tiene i + 1 cuando envuelve sus promesas. Además, al hacer este tipo de cosas, simplemente desenvuelva la primera iteración. var promise = processItem (datos [0]); for (var i = 1; i <data.length; i ++) {promise = $ .when (promise, processItem (data [i])); }
MPavlak