O que $ .when.apply ($, someArray) faz?

110

Estou lendo sobre diferidos e promessas e continuo descobrindo $.when.apply($, someArray). Não estou certo do que isso faz exatamente, procurando uma explicação de que uma linha funciona exatamente (não o trecho de código inteiro). Aqui está algum 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
fonte
1
.done()pode ser usado no lugar de .thenneste caso, apenas para sua informação
Kevin B
2
fwiw, há uma porta adiada para sublinhar que permite passar um único array para _.whenque você não precise usarapply
Eevee
Saiba mais sobre .apply: developer.mozilla.org/en-US/docs/JavaScript/Reference/… .
Felix Kling
relacionado : stackoverflow.com/questions/1986896/…
Felix Kling
1
O artigo ao qual o OP se refere em sua primeira frase mudou de local - agora está em: flaviocopes.com/blog/deferreds-and-promises-in-javascript .
glaucon

Respostas:

161

.applyé usado para chamar uma função com uma matriz de argumentos. Ele pega cada elemento na matriz e usa cada um como um parâmetro para a função. .applytambém pode alterar o context ( this) dentro de uma função.

Então, vamos pegar $.when. Costuma-se dizer "quando todas essas promessas forem resolvidas ... faça algo". Leva um número infinito (variável) de parâmetros.

No seu caso, você tem uma série de promessas; você não sabe para quantos parâmetros está passando $.when. Passar o próprio array para $.whennão funcionaria, porque ele espera que seus parâmetros sejam promessas, não um array.

É aí que .applyentra. Ele pega a matriz e chama $.whencada elemento como um parâmetro (e garante que thisesteja definido como jQuery/ $), então tudo funciona :-)

Foguete Hazmat
fonte
3
quando várias promessas são passadas para o método $ .when. Em que ordem eles serão executados? um após o outro ou em paralelo?
Darshan
21
@Darshan: Você não "executa" promessas. Você espera que eles sejam resolvidos. Eles são executados quando criados, $.whenbasta esperar que todos eles sejam finalizados antes de continuar.
Rocket Hazmat
1
e quanto à diferença entre $.when($, arrayOfPromises).done(...) e $.when(null, arrayOfPromises).done(...) (que eu encontrei como soluções propostas nos fóruns ...)
zeroquaranta
63

$ .when pega qualquer número de parâmetros e resolve quando todos eles foram resolvidos.

anyFunction .apply (thisValue, arrayParameters) chama a função anyFunction definindo seu contexto (thisValue será o this dentro da chamada de função) e passa todos os objetos em arrayParameters como parâmetros individuais.

Por exemplo:

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

É o mesmo que:

$.when(def1, def2)

Mas a forma de chamar apply permite que você passe uma matriz de um número desconhecido de parâmetros. (Em seu código, você está dizendo que seus dados vêm de um serviço, então essa é a única maneira de chamar $ .when )

Pablo
fonte
15

Aqui, o código totalmente 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
fonte
7
$.when.apply($, array)não é o mesmo que $.when(array). É o mesmo que:$.when(array[0], array[1], ...)
Rocket Hazmat
1
Essa é a principal razão pela qual é usado com .apply , você não sabe quantos elementos processItemsDeferred tem
Pablo
2

Infelizmente não posso concordar com vocês.

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

Chamará everythingDoneassim que um adiado for rejeitado , mesmo se houver outros adiados pendentes .

Aqui está o script completo (eu recomendo 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');
}

É um bug? Eu gostaria de usar isso como o cavalheiro acima descreveu.

user3388213
fonte
1
A primeira rejeição disparará o sempre, mas não o. Depois. Veja meu jsfiddle.net/logankd/s5dacgb3 que fiz a partir de seu exemplo. Estou usando JQuery 2.1.0 neste exemplo.
Alinhado em
1
Isso é o pretendido. Existem muitos casos em que você gostaria de saber assim que algo falhar, não esperar que tudo seja concluído e verificar se houve falhas. Especialmente se o processamento não pode continuar após qualquer falha, por que esperar o resto terminar / falhar? Como o outro comentário sugeriu, você pode usar o par .then ou .fail e .done.
MPavlak de
@GoneCoding Não é útil. O OP perguntou o que o apply () faz e você sugeriu uma alternativa terrível que nunca deveria ser usada :) É para isso que serve o botão de voto negativo. Eu também não usei até que você se recusou a fornecer PORQUE fez isso e por quê (mais do que sua preferência em evitar matrizes por algum motivo)
MPavlak
@GoneCoding Obrigado por anotar essa resposta
MPavlak
1
@GoneCoding lol, eu li sua solução e forneceu feedback. você não respondeu à pergunta original. Você não poderia explicar por que era assim. São pessoas como você que oferecem soluções terríveis para as pessoas que estão aprendendo. Você claramente tem habilidade limitada em javascript e está me considerando o n00b. Eu indiquei por que estava errado e você não conseguiu nem ler o código e, em vez disso, me disse que estou errado. bom trabalho amigo!
MPavlak
1

Talvez alguém possa achar isso útil:

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

everythingDone não é chamado em caso de rejeição

Vlado Kurelec
fonte
0

$ .when sozinho torna possível que um retorno de chamada seja chamado quando todas as promessas passadas a ele forem resolvidas / rejeitadas. Normalmente, $ .quando recebe um número variável de argumentos, usar .apply torna possível passar uma matriz de argumentos, é muito poderoso. Para obter mais informações sobre .apply: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/apply

Roger C
fonte
0

Obrigado pela sua solução elegante:

var promise;

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

promise.then(everythingDone);

Apenas um ponto: ao usar resolveWithpara obter alguns parâmetros, ele quebra por causa da promessa inicial definida como indefinida. O que eu fiz para fazer funcionar:

// 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
fonte
2
Embora funcione, não é realmente elegante. você está criando promessas que representam o último adiamento sendo feito de modo que a última iteração contenha "quando (workToDo [0..i-1], workToDo [i])" ou mais claramente "quando todo o trabalho anterior e este trabalho feito". Isso significa que você tem i + 1 ao encerrar suas promessas. Além disso, ao fazer esse tipo de coisa, simplesmente desembrulhe a primeira iteração. var promessa = processItem (dados [0]); para (var i = 1; i <data.length; i ++) {promessa = $ .when (promessa, processItem (dados [i])); }
MPavlak