Retorno de chamada após todos os retornos asynchronouss para cada retorno de chamada

Como o título sugere. Como eu faço isso?

Eu quero chamar whenAllDone() depois que o forEach-loop passou por cada elemento e fez algum processamento asynchronous.

 [1, 2, 3].forEach( function(item, index, array, done) { asyncFunction(item, function itemDone() { console.log(item + " done"); done(); }); }, function allDone() { console.log("All done"); whenAllDone(); } ); 

Possível fazê-lo funcionar assim? Quando o segundo argumento para forEach é uma function de retorno de chamada que é executada uma vez que passou por todas as iterações?

Produção esperada:

 3 done 1 done 2 done All done! 

Array.forEach não fornece esta minúcia (oh, se fosse), mas existem várias maneiras de realizar o que você deseja:

Usando um contador simples

 function callback () { console.log('all done'); } var itemsProcessed = 0; [1, 2, 3].forEach((item, index, array) => { asyncFunction(item, () => { itemsProcessed++; if(itemsProcessed === array.length) { callback(); } }); }); 

(graças a @vanuan e outros) Essa abordagem garante que todos os itens sejam processados ​​antes de chamar o retorno de chamada “concluído”. A abordagem que Emil sugere, embora seja tipicamente eficaz em minha experiência, não oferece a mesma garantia.

Usando Promessas ES6

(uma biblioteca de promises pode ser usada para navegadores mais antigos):

  1. Processe todas as solicitações garantindo a execução síncrona (por exemplo, 1, 2, 3).

     function asyncFunction (item, cb) { setTimeout(() => { console.log('done with', item); cb(); }, 100); } let requests = [1, 2, 3].reduce((promiseChain, item) => { return promiseChain.then(() => new Promise((resolve) => { asyncFunction(item, resolve); })); }, Promise.resolve()); requests.then(() => console.log('done')) 
  2. Processe todas as solicitações assíncronas sem a execução “síncrona” (2 podem terminar mais rápido que 1)

     let requests = [1,2,3].map((item) => { return new Promise((resolve) => { asyncFunction(item, resolve); }); }) Promise.all(requests).then(() => console.log('done')); 

Usando uma biblioteca assíncrona

Existem outras bibliotecas assíncronas, sendo async as mais populares, que fornecem mecanismos para expressar o que você deseja.

Editar


O corpo da pergunta foi editado para remover o código de exemplo anteriormente síncrono, então atualizei minha resposta para esclarecer. O exemplo original usou um código similar síncrono para modelar o comportamento asynchronous, de modo que o seguinte foi aplicado:

array.forEach é síncrono e por isso é res.write , então você pode simplesmente colocar seu retorno de chamada após sua chamada para foreach:

  posts.foreach(function(v, i) { res.write(v + ". index " + i); }); res.end(); 

Se você encontrar funções assíncronas e quiser ter certeza de que, antes de executar o código, ele conclua sua tarefa, sempre podemos usar o recurso de retorno de chamada.

por exemplo:

 var ctr = 0; posts.forEach(function(element, index, array){ asynchronous(function(data){ ctr++; if (ctr === array.length) { functionAfterForEach(); } }) }); 

note: functionAfterForEach é uma function a ser executada depois que todas as tarefas terminarem. Assíncrono é a function assíncrona executada dentro do foreach.

espero que isto ajude.

É estranho quantas respostas incorretas foram dadas ao caso asynchronous ! Pode ser simplesmente mostrado que o índice de verificação não fornece o comportamento esperado:

 // INCORRECT var list = [4000, 2000]; list.forEach(function(l, index) { console.log(l + ' started ...'); setTimeout(function() { console.log(index + ': ' + l); }, l); }); 

saída:

 4000 started 2000 started 1: 2000 0: 4000 

Se verificarmos o index === array.length - 1 , o retorno de chamada será chamado após a conclusão da primeira iteração, enquanto o primeiro elemento ainda está pendente!

Para resolver este problema sem usar bibliotecas externas como async, acho que sua melhor aposta é salvar o tamanho da lista e decrementar se após cada iteração. Como há apenas um segmento, temos certeza de que não há chance de condição de corrida.

 var list = [4000, 2000]; var counter = list.length; list.forEach(function(l, index) { console.log(l + ' started ...'); setTimeout(function() { console.log(index + ': ' + l); counter -= 1; if ( counter === 0) // call your callback here }, l); }); 

Espero que isso corrija o seu problema, eu costumo trabalhar com isso quando preciso executar paraEi com tarefas assíncronas dentro.

 foo = [a,b,c,d]; waiting = foo.length; foo.forEach(function(entry){ doAsynchronousFunction(entry,finish) //call finish after each entry } function finish(){ waiting--; if (waiting==0) { //do your Job intended to be done after forEach is completed } } 

com

 function doAsynchronousFunction(entry,callback){ //asynchronousjob with entry callback(); } 

Minha solução sem promise (isso garante que cada ação seja finalizada antes do próximo):

 Array.prototype.forEachAsync = function (callback, end) { var self = this; function task(index) { var x = self[index]; if (index >= self.length) { end() } else { callback(self[index], index, self, function () { task(index + 1); }); } } task(0); }; var i = 0; var myArray = Array.apply(null, Array(10)).map(function(item) { return i++; }); console.log(JSON.stringify(myArray)); myArray.forEachAsync(function(item, index, arr, next){ setTimeout(function(){ $(".toto").append("
item index " + item + " done
"); console.log("action " + item + " done"); next(); }, 300); }, function(){ $(".toto").append("
ALL ACTIONS ARE DONE
"); console.log("ALL ACTIONS ARE DONE"); });
  

Com o ES2018, você pode usar iteradores asynchronouss:

 const asyncFunction = a => fetch(a); const itemDone = a => console.log(a); async function example() { const arrayOfFetchPromises = [1, 2, 3].map(asyncFunction); for await (const item of arrayOfFetchPromises) { itemDone(item); } console.log('All done'); } 

Esta é a solução para o Node.js, que é asynchronous.

usando o pacote async npm.

(JavaScript) Sincronizando o ForEach Loop com callbacks dentro

Como sobre setInterval, para verificar a contagem completa de iteração, traz garantia. não tenho certeza se não vai sobrecarregar o escopo embora, mas eu usá-lo e parece ser o único

 _.forEach(actual_JSON, function (key, value) { // run any action and push with each iteration array.push(response.id) }); setInterval(function(){ if(array.length > 300) { callback() } }, 100); 

Minha solução:

 //Object forEachDone Object.defineProperty(Array.prototype, "forEachDone", { enumerable: false, value: function(task, cb){ var counter = 0; this.forEach(function(item, index, array){ task(item, index, array); if(array.length === ++counter){ if(cb) cb(); } }); } }); //Array forEachDone Object.defineProperty(Object.prototype, "forEachDone", { enumerable: false, value: function(task, cb){ var obj = this; var counter = 0; Object.keys(obj).forEach(function(key, index, array){ task(obj[key], key, obj); if(array.length === ++counter){ if(cb) cb(); } }); } }); 

Exemplo:

 var arr = ['a', 'b', 'c']; arr.forEachDone(function(item){ console.log(item); }, function(){ console.log('done'); }); // out: abc done 

Eu tento Easy Way para resolvê-lo, compartilhe com você:

 let counter = 0; arr.forEach(async (item, index) => { await request.query(item, (err, recordset) => { if (err) console.log(err); //do Somthings counter++; if(counter == tableCmd.length){ sql.close(); callback(); } }); 

request é Função da Biblioteca mssql no Nó js. Isso pode replace cada function ou código desejado. Boa sorte

 var i=0; const waitFor = (ms) => { new Promise((r) => { setTimeout(function () { console.log('timeout completed: ',ms,' : ',i); i++; if(i==data.length){ console.log('Done') } }, ms); }) } var data=[1000, 200, 500]; data.forEach((num) => { waitFor(num) }) 

Uma solução simples seria como seguir

 function callback(){console.log("i am done");} ["a", "b", "c"].forEach(function(item, index, array){ //code here if(i == array.length -1) callback() } 

Você não deve precisar de um retorno de chamada para percorrer uma lista. Basta adicionar a chamada end() após o loop.

 posts.forEach(function(v, i){ res.write(v + ". Index " + i); }); res.end();