Passe em uma matriz de Diferidos para $ .quando ()

Aqui está um exemplo artificial do que está acontecendo: http://jsfiddle.net/adamjford/YNGcm/20/

HTML:

Click me! 

JavaScript:

 function getSomeDeferredStuff() { var deferreds = []; var i = 1; for (i = 1; i <= 10; i++) { var count = i; deferreds.push( $.post('/echo/html/', { html: "

Task #" + count + " complete.", delay: count }).success(function(data) { $("div").append(data); })); } return deferreds; } $(function() { $("a").click(function() { var deferreds = getSomeDeferredStuff(); $.when(deferreds).done(function() { $("div").append("

All done!

"); }); }); });

Eu quero “tudo feito!” para aparecer depois que todas as tarefas adiadas forem concluídas, mas $.when() não parece saber como lidar com uma matriz de objects Deferred. “Tudo feito!” está acontecendo primeiro porque o array não é um object Deferred, então o jQuery vai em frente e assume que acabou de terminar.

Eu sei que alguém poderia passar os objects para a function como $.when(deferred1, deferred2, ..., deferredX) mas é desconhecido quantos objects Deferred haverá em execução no problema real que estou tentando resolver.

Para passar uma matriz de valores para qualquer function que normalmente espera que eles sejam parâmetros separados, use Function.prototype.apply , portanto, nesse caso, você precisa:

 $.when.apply($, my_array).then( ___ ); 

Veja http://jsfiddle.net/YNGcm/21/

No ES6, você pode usar o ... spread operator em vez disso:

 $.when(...my_array).then( ___ ); 

Em ambos os casos, uma vez que é improvável que você saiba com antecedência quantos parâmetros formais o manipulador precisará, esse manipulador precisaria processar a matriz de arguments para recuperar o resultado de cada promise.

As soluções alternativas acima (obrigado!) Não resolvem adequadamente o problema de recuperar os objects fornecidos para o método resolve() do deferred porque o jQuery chama os callbacks done() e fail() com parâmetros individuais, não um array. Isso significa que temos que usar a pseudo-matriz de arguments para obter todos os objects resolvidos / rejeitados retornados pela matriz de deferreds, que é feia:

 $.when.apply($,deferreds).then(function() { var objects=arguments; // The array of resolved objects as a pseudo-array ... }; 

Desde que passamos em uma matriz de diferidos, seria bom recuperar uma série de resultados. Também seria bom recuperar um array real em vez de um pseudo array para que possamos usar methods como Array.sort() .

Aqui está uma solução inspirada no método when.all() que trata desses problemas:

 // Put somewhere in your scripting environment if (typeof jQuery.when.all === 'undefined') { jQuery.when.all = function (deferreds) { return $.Deferred(function (def) { $.when.apply(jQuery, deferreds).then( function () { def.resolveWith(this, [Array.prototype.slice.call(arguments)]); }, function () { def.rejectWith(this, [Array.prototype.slice.call(arguments)]); }); }); } } 

Agora você pode simplesmente passar uma matriz de adiamentos / promises e recuperar uma matriz de objects resolvidos / rejeitados no seu retorno de chamada, assim:

 $.when.all(deferreds).then(function(objects) { console.log("Resolved objects:", objects); }); 

Você pode aplicar o método when ao seu array:

 var arr = [ /* Deferred objects */ ]; $.when.apply($, arr); 

Como você trabalha com uma matriz de jQuery Deferreds?

Ao chamar várias chamadas paralelas do AJAX, você tem duas opções para manipular as respectivas respostas.

  1. Use a chamada Synchronous AJAX / um após o outro / não recomendado
  2. Use Promises' array Promises' e $.when aceitar as promise e seu callback .done será chamado quando todas as promise forem retornadas com sucesso com as respectivas respostas.

Exemplo

 function ajaxRequest(capitalCity) { return $.ajax({ url: 'https://restcountries.eu/rest/v1/capital/'+capitalCity, success: function(response) { }, error: function(response) { console.log("Error") } }); } $(function(){ var capitalCities = ['Delhi', 'Beijing', 'Washington', 'Tokyo', 'London']; $('#capitals').text(capitalCities); function getCountryCapitals(){ //do multiple parallel ajax requests var promises = []; for(var i=0,l=capitalCities.length; i 
  

Capital Cities :

Respective Country's Native Names :

Como uma alternativa simples, que não requer $.when.apply ou uma array , você pode usar o seguinte padrão para gerar uma única promise para múltiplas promises paralelas:

 promise = $.when(promise, anotherPromise); 

por exemplo

 function GetSomeDeferredStuff() { // Start with an empty resolved promise (or undefined does the same!) var promise; var i = 1; for (i = 1; i < = 5; i++) { var count = i; promise = $.when(promise, $.ajax({ type: "POST", url: '/echo/html/', data: { html: "

Task #" + count + " complete.", delay: count / 2 }, success: function (data) { $("div").append(data); } })); } return promise; } $(function () { $("a").click(function () { var promise = GetSomeDeferredStuff(); promise.then(function () { $("div").append("

All done!

"); }); }); });

Notas:

  • Eu percebi isso depois de ver alguém cadeia promete sequencialmente, usando promise = promise.then(newpromise)
  • A desvantagem é que cria objects extras promissores nos bastidores e quaisquer parâmetros passados ​​no final não são muito úteis (já que estão nesteds dentro de objects adicionais). Para o que você quer, porém, é curto e simples.
  • A vantagem é que não requer gerenciamento de matriz ou matriz.

Eu quero propor outro usando $ .each:

  1. Podemos declarar a function ajax como:

     function ajaxFn(someData) { this.someData = someData; var that = this; return function () { var promise = $.Deferred(); $.ajax({ method: "POST", url: "url", data: that.someData, success: function(data) { promise.resolve(data); }, error: function(data) { promise.reject(data); } }) return promise; } } 
  2. Parte do código onde criamos uma matriz de funções com o ajax para enviar:

     var arrayOfFn = []; for (var i = 0; i < someDataArray.length; i++) { var ajaxFnForArray = new ajaxFn(someDataArray[i]); arrayOfFn.push(ajaxFnForArray); } 
  3. E chamando funções com o envio de ajax:

     $.when( $.each(arrayOfFn, function(index, value) { value.call() }) ).then(function() { alert("Cheer!"); } ) 

Se você estiver transpilando e tiver access ao ES6, poderá usar a syntax de propagação, que aplica especificamente cada item iterável de um object como um argumento discreto, exatamente como $.when() precisa.

 $.when(...deferreds).done(() => { // do stuff }); 

Link MDN – Sintaxe de propagação

Eu tive um caso muito parecido onde eu estava postando em cada loop e, em seguida, definindo a marcação html em alguns campos de números recebidos do ajax. Em seguida, precisei fazer uma sum dos valores (agora atualizados) desses campos e colocá-los em um campo total.

Assim, o problema era que eu estava tentando fazer uma sum em todos os números, mas nenhum dado havia chegado ainda das chamadas ajax assíncronas. Eu precisava concluir essa funcionalidade em algumas funções para poder reutilizar o código. Minha function externa aguarda os dados antes de eu ir e fazer algumas coisas com o DOM totalmente atualizado.

  // 1st function Outer() { var deferreds = GetAllData(); $.when.apply($, deferreds).done(function () { // now you can do whatever you want with the updated page }); } // 2nd function GetAllData() { var deferreds = []; $('.calculatedField').each(function (data) { deferreds.push(GetIndividualData($(this))); }); return deferreds; } // 3rd function GetIndividualData(item) { var def = new $.Deferred(); $.post('@Url.Action("GetData")', function (data) { item.html(data.valueFromAjax); def.resolve(data); }); return def; } 

Se você estiver usando angularJS ou alguma variante da biblioteca Q promise, então você tem um método .all() que resolve esse problema exato.

 var savePromises = []; angular.forEach(models, function(model){ savePromises.push( model.saveToServer() ) }); $q.all(savePromises).then( function success(results){...}, function failed(results){...} ); 

veja a API completa:

https://github.com/kriskowal/q/wiki/API-Reference#promiseall

https://docs.angularjs.org/api/ng/service/$q