Chamando uma function assíncrona dentro de um loop for em JavaScript

Eu tenho o seguinte código:

for(var i = 0; i < list.length; i++){ mc_cli.get(list[i], function(err, response) { do_something(i); }); } 

mc_cli é uma conexão com um database do memcached. Como você pode imaginar, a function de retorno de chamada é assíncrona, portanto, pode ser executada quando o loop for já terminou. Além disso, ao chamar dessa maneira do_something(i) ele sempre usa o último valor do loop for.

Eu tentei com um fechamento dessa maneira

 do_something((function(x){return x})(i)) 

mas aparentemente isso é novamente usando sempre o último valor do índice do loop for.

Eu também tentei declarar uma function antes do loop for assim:

 var create_closure = function(i) { return function() { return i; } } 

e depois chamando

 do_something(create_closure(i)()) 

mas novamente sem sucesso, com o valor de retorno sendo sempre o último valor do loop for.

Alguém pode me dizer o que estou fazendo de errado com fechamentos? Eu pensei que os entendia, mas não consigo entender por que isso não está funcionando.

Como você está executando um array, você pode simplesmente usar forEach que fornece o item da lista e o índice no retorno de chamada. A iteração terá seu próprio escopo.

 list.forEach(function(listItem, index){ mc_cli.get(listItem, function(err, response) { do_something(index); }); }); 

Esse é o paradigma de function assíncrona dentro de um loop, e eu costumo lidar com isso usando uma function anônima imediatamente invocada. Isso garante que as funções assíncronas sejam chamadas com o valor correto da variável de índice.

Certo, ótimo. Assim, todas as funções assíncronas foram iniciadas e o loop sai. Agora, não há como dizer quando essas funções serão concluídas, devido à sua natureza assíncrona ou em que ordem serão concluídas. Se você tiver um código que precise aguardar até que todas essas funções sejam concluídas antes da execução, recomendo manter uma contagem simples de quantas funções foram concluídas:

 var total = parsed_result.list.length; var count = 0; for(var i = 0; i < total; i++){ (function(foo){ mc_cli.get(parsed_result.list[foo], function(err, response) { do_something(foo); count++; if (count > total - 1) done(); }); }(i)); } // You can guarantee that this function will not be called until ALL of the // asynchronous functions have completed. function done() { console.log('All data has been loaded :).'); } 

Você estava muito perto, mas você deve passar o fechamento para get vez de colocá-lo dentro do retorno de chamada:

 function createCallback(i) { return function(){ do_something(i); } } for(var i = 0; i < list.length; i++){ mc_cli.get(list[i], createCallback(i)); } 

Eu sei que isso é um tópico antigo, mas, de qualquer forma, adiciono minha resposta. O ES2015 let tem o recurso de religar a variável de loop em cada iteração, portanto, ele mantém o valor da variável de loop em retornos de chamada asynchronouss, para que você possa tentar o seguinte:

 for(let i = 0; i < list.length; i++){ mc_cli.get(list[i], function(err, response) { do_something(i); }); } 

Mas, de qualquer forma, é melhor usar forEach ou criar um encerramento usando a function imediatamente invocada, já que let é o recurso ES2015 e pode não ser compatível com todos os navegadores e implementações. A partir daqui, em Bindings ->let->for/for-in loop iteration scope eu posso ver que ele não é suportado até o Edge 13 e até o Firefox 49 (eu não chequei nesses navegadores). Ele até diz que não é suportado com o Node 4, mas eu pessoalmente testei e parece que é suportado.

Tente isto, usando a syntax async/await e Promise

 (async function() { for(var i = 0; i < list.length; i++){ await new Promise(next => { mc_cli.get(list[i], function(err, response) { do_something(i); next() }) }) } })() 

Isso interromperá o loop em cada ciclo até que a next() function next() seja acionada

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); }); } for (let i = 1; i < 5; i++) { await XHRpost(i); } 

Se você deseja executar funções assíncronas dentro de um loop, mas ainda deseja manter o índice ou outras variables ​​após a execução de um retorno de chamada, é possível include seu código em uma expressão de function IIFE (chamada imediatamente).

 var arr = ['Hello', 'World', 'Javascript', 'Async', ':)']; for( var i = 0; i < arr.length; i++) { (function(index){ setTimeout(function(){ console.log(arr[index]); }, 500); 

Usando o ES6 (typescript) você pode usar os benefícios do async e await :

 let list: number[] = [1, 2, 3, 4, 5]; // this is async fucntion function do_something(counter: number): Promise { return new Promise((resolve, reject) => { setTimeout(() => { console.log('called after ' + counter + ' seconds'); resolve(counter); }, counter * 1000); }) } async function foo() { // itrate over list and wait for when everything is finished let data = await Promise.all(list.map(async i => await do_something(i))); console.log(data); } foo();