Richiamata dopo tutto il callback asincrono di Each sono completati

Come suggerisce il titolo. Come faccio a fare questo?

Voglio chiamare whenAllDone() dopo che forEach-loop ha attraversato ogni elemento e fatto un po ‘di elaborazione asincrona.

 [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(); } ); 

Possibile farlo funzionare così? Quando il secondo argomento di forEach è una funzione di callback che viene eseguita una volta passata attraverso tutte le iterazioni?

Uscita prevista:

 3 done 1 done 2 done All done! 

Array.forEach non fornisce questa simpatia (oh se lo fosse) ma ci sono diversi modi per realizzare ciò che vuoi:

Utilizzando un semplice contatore

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

(grazie a @vanuan e altri) Questo approccio garantisce che tutti gli articoli vengano elaborati prima di richiamare il callback “done”. L’approccio suggerito da Emil, sebbene di solito efficace nella mia esperienza, non fornisce la stessa garanzia.

Usando Promesse ES6

(una libreria di promise può essere utilizzata per i browser più vecchi):

  1. Elabora tutte le richieste garantendo l’esecuzione sincrona (ad es. 1 poi 2 poi 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. Elabora tutte le richieste asincrone senza esecuzione “sincrona” (2 può finire più velocemente di 1)

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

Utilizzando una libreria asincrona

Esistono altre librerie asincrone, async la più popolare, che fornisce meccanismi per esprimere ciò che si desidera.

modificare


Il corpo della domanda è stato modificato per rimuovere il codice di esempio precedentemente sincrono, quindi ho aggiornato la mia risposta per chiarire. L’esempio originale ha utilizzato il codice sincrono come modello per modellare il comportamento asincrono, pertanto è stato applicato quanto segue:

array.forEach è sincrono e quindi res.write , quindi puoi semplicemente mettere la tua richiamata dopo la chiamata a foreach:

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

se incontri delle funzioni asincrone e vuoi assicurarti che prima di eseguire il codice finisca il suo compito, possiamo sempre usare la capacità di callback.

per esempio:

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

nota: functionAfterForEach è la funzione da eseguire dopo che le attività foreach sono terminate. asincrono è la funzione asincrona eseguita all’interno di foreach.

spero che questo ti aiuti.

È strano quante risposte errate siano state date al caso asincrono ! Si può semplicemente mostrare che il controllo dell’indice non fornisce il comportamento previsto:

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

produzione:

 4000 started 2000 started 1: 2000 0: 4000 

Se controlliamo l’ index === array.length - 1 , la richiamata verrà richiamata al completamento della prima iterazione, mentre il primo elemento è ancora in sospeso!

Per risolvere questo problema senza l’utilizzo di librerie esterne come asincrone, penso che la soluzione migliore sia quella di salvare la lunghezza della lista e il decremento se dopo ogni iterazione. Dato che c’è solo una discussione, siamo sicuri che non ci sono possibilità di correre.

 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); }); 

Spero che questo risolva il tuo problema, di solito lavoro con questo quando ho bisogno di eseguire forEach con compiti asincroni all’interno.

 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 } } 

con

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

La mia soluzione senza Promessa (questo garantisce che ogni azione sia conclusa prima che inizi il prossimo):

 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"); });
  

Con ES2018 puoi usare iteratori asincroni:

 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'); } 

Questa è la soluzione per Node.js che è asincrono.

utilizzando il pacchetto async npm.

(JavaScript) Sincronizzazione forEach Loop con callback all’interno

Che ne dite di setInterval, per verificare il conteggio completo dell’iterazione, porta la garanzia. non sono sicuro se non sovraccaricherà lo scope, ma lo uso e sembra essere l’unico

 _.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); 

La mia soluzione:

 //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(); } }); } }); 

Esempio:

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

Provo Easy Way per risolverlo, condividerlo con te:

 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 è Funzione della libreria mssql nel nodo js. Questo può sostituire ogni funzione o codice che vuoi. In bocca al lupo

 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) }) 

Una soluzione semplice sarebbe come seguire

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

Non dovresti aver bisogno di una richiamata per iterare attraverso un elenco. Basta aggiungere la chiamata end() dopo il ciclo.

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