Processo asynchronous dentro de um javascript for loop

Estou executando um loop de events do seguinte formato:

var i; var j = 10; for (i = 0; i < j; i++) { asynchronousProcess(callbackFunction() { alert(i); }); } 

Eu estou tentando exibir uma série de alertas mostrando os números de 0 a 10. O problema é que no momento em que a function de retorno de chamada é acionada, o loop já passou por algumas iterações e exibe um valor mais alto de i . Alguma recomendação sobre como corrigir isso?

O loop for é executado imediatamente para conclusão enquanto todas as suas operações assíncronas são iniciadas. Quando eles completarem algum tempo no futuro e chamarem seus callbacks, o valor de sua variável de índice de loop será seu último valor para todos os callbacks.

Isso ocorre porque o loop for não aguarda a conclusão de uma operação assíncrona antes de prosseguir para a próxima iteração do loop e porque os retornos de chamada asynchronouss são chamados em algum momento no futuro. Assim, o loop conclui suas iterações e, em seguida, os callbacks são chamados quando essas operações assíncronas são concluídas. Assim, o índice de loop é “concluído” e está no valor final de todos os retornos de chamada.

Para contornar isso, você tem que salvar exclusivamente o índice de loop separadamente para cada retorno de chamada. Em Javascript, a maneira de fazer isso é capturá-lo em um encerramento de function. Isso pode ser feito criando um fechamento de function in-line especificamente para esse propósito (primeiro exemplo mostrado abaixo) ou você pode criar uma function externa para a qual você passa o índice e deixá-lo manter o índice exclusivamente para você (segundo exemplo mostrado abaixo).

A partir de 2016, se você tiver uma implementação do Javascript totalmente funcional do ES6, você também pode usar o let para definir a variável for loop e ela será definida exclusivamente para cada iteração do loop for (terceira implementação abaixo). Mas observe que esse é um recurso de implementação tardia nas implementações do ES6, portanto, você precisa garantir que o ambiente de execução ofereça suporte a essa opção.

Use .forEach () para iterar, pois cria seu próprio fechamento de function

 someArray.forEach(function(item, i) { asynchronousProcess(function(item) { console.log(i); }); }); 

Crie seu próprio fechamento de function usando um IIFE

 var j = 10; for (var i = 0; i < j; i++) { (function(cntr) { // here the value of i was passed into as the argument cntr // and will be captured in this function closure so each // iteration of the loop can have it's own value asynchronousProcess(function() { console.log(cntr); }); })(i); } 

Criar ou modificar a function externa e passar a variável

Se você puder modificar a function asynchronousProcess() , poderá simplesmente passar o valor para lá e fazer com que o asynchronousProcess() funcione o cntr de volta ao retorno de chamada, desta forma:

 var j = 10; for (var i = 0; i < j; i++) { asynchronousProcess(i, function(cntr) { console.log(cntr); }); } 

Use ES6 let

Se você tiver um ambiente de execução Javascript que suporte totalmente o ES6, você pode usar o let in em seu loop for assim:

 const j = 10; for (let i = 0; i < j; i++) { asynchronousProcess(function() { console.log(i); }); } 

let declarado em uma declaração de loop for como este irá criar um valor único de i para cada chamada do loop (que é o que você quer).

Serializando com promises e async / await

Se sua function assíncrona retorna uma promise, e você deseja serializar suas operações assíncronas para executar uma após a outra em vez de em paralelo e você está executando em um ambiente moderno que suporta async e await , então você tem mais opções.

 async function someFunction() { const j = 10; for (let i = 0; i < j; i++) { // wait for the promise to resolve before advancing the for loop await asynchronousProcess(); console.log(i); } } 

Isso fará com que apenas uma chamada para asynchronousProcess() esteja em andamento por vez, e o loop for não avançará até que cada uma seja concluída. Isso é diferente dos esquemas anteriores que executavam suas operações assíncronas em paralelo, de modo que dependiam inteiramente do design desejado. Nota: await trabalhos com uma promise, para que sua function retorne uma promise que seja resolvida / rejeitada quando a operação assíncrona for concluída. Além disso, observe que, para usar o await , a function de contenção deve ser declarada como async .

Alguma recomendação sobre como corrigir isso?

De várias. Você pode usar bind :

 for (i = 0; i < j; i++) { asycronouseProcess(function (i) { alert(i); }.bind(null, i)); } 

Ou, se o seu navegador suportar let (será na próxima versão do ECMAScript, no entanto o Firefox já suporta isso há algum tempo) você poderia ter:

 for (i = 0; i < j; i++) { let k = i; asycronouseProcess(function() { alert(k); }); } 

Ou, você poderia fazer o trabalho de bind manualmente (no caso do navegador não suportá-lo, mas eu diria que você pode implementar um shim nesse caso, deve estar no link acima):

 for (i = 0; i < j; i++) { asycronouseProcess(function(i) { return function () { alert(i) } }(i)); } 

Eu geralmente prefiro let quando eu posso usá-lo (por exemplo, para o Firefox add-on); caso contrário, bind ou uma function de curry personalizada (que não precisa de um object de contexto).

async await await está aqui (ES7), então você pode fazer esse tipo de coisa com muita facilidade agora.

  var i; var j = 10; for (i = 0; i < j; i++) { await asycronouseProcess(); alert(i); } 

Lembre-se, isso funciona somente se o asycronouseProcess estiver retornando uma Promise

Se o asycronouseProcess não estiver no seu controle, você pode fazer com que ele retorne uma Promise por si mesmo assim

 function asyncProcess() { return new Promise((resolve, reject) => { asycronouseProcess(()=>{ resolve(); }) }) } 

Em seguida, substitua esta linha e await asycronouseProcess(); por await asyncProcess();

Noções Promises antes mesmo de olhar para async await é preciso (Leia também sobre o suporte para async await )

 var i = 0; var length = 10; function for1() { console.log(i); for2(); } function for2() { if (i == length) { return false; } setTimeout(function() { i++; for1(); }, 500); } for1(); 

O código JavaScript é executado em um único encadeamento, portanto, você não pode bloquear principalmente para aguardar a conclusão da iteração do primeiro loop antes de iniciar o próximo, sem afetar seriamente a usabilidade da página.

A solução depende do que você realmente precisa. Se o exemplo estiver próximo de exatamente o que você precisa, a sugestão do @ Simon de passar o i ao seu processo asynchronous é boa.

ES2017: Você pode envolver o código asynchronous dentro de uma function (por exemplo, XHRPost) retornando uma promise (código asynchronous dentro da promise).

Em seguida, chame a function (XHRPost) dentro do loop for, mas com a palavra-chave Await mágica. 🙂

 let http = new XMLHttpRequest(); let url = 'http://sumersin/forum.social.json'; function XHRpost(i) { return new Promise(function(resolve) { let params = 'id=nobot&%3Aoperation=social%3AcreateForumPost&subject=Demo' + i + '&message=Here%20is%20the%20Demo&_charset_=UTF-8'; http.open('POST', url, true); http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); http.onreadystatechange = function() { console.log("Done " + i + "< <<<>>>>>" + http.readyState); if(http.readyState == 4){ console.log('SUCCESS :',i); resolve(); } } http.send(params); }); } (async () => { for (let i = 1; i < 5; i++) { await XHRpost(i); } })();