Chiamando una funzione asincrona all’interno di un ciclo for in JavaScript

Ho il codice seguente:

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

mc_cli è una connessione a un database memcached. Come potete immaginare, la funzione di callback è asincrona, quindi può essere eseguita quando il ciclo for è già terminato. Inoltre, quando si chiama in questo modo do_something(i) usa sempre l’ultimo valore del ciclo for.

Ho provato con una chiusura in questo modo

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

ma a quanto pare questo usa di nuovo sempre l’ultimo valore dell’indice del ciclo for.

Ho anche provato a dichiarare una funzione prima del ciclo for in questo modo:

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

e poi chiamando

 do_something(create_closure(i)()) 

ma ancora senza successo, con il valore di ritorno che è sempre l’ultimo valore del ciclo for.

Qualcuno può dirmi cosa sto sbagliando con chiusure? Pensavo di averli compresi ma non riesco a capire perché non funzioni.

    Dato che stai eseguendo un array, puoi semplicemente usare forEach che fornisce la voce di elenco e l’indice nel callback. L’iterazione avrà il suo scopo.

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

    Questo è il paradigma asincrono-funzione-dentro-un-ciclo, e di solito lo gestisco usando una funzione anonima-invocata immediatamente. Ciò garantisce che le funzioni asincrone vengano chiamate con il valore corretto della variabile index.

    Ok, fantastico. Quindi tutte le funzioni asincrone sono state avviate e il ciclo termina. Ora, non si sa quando queste funzioni verranno completate, a causa della loro natura asincrona o in quale ordine verranno completate. Se si dispone di codice che deve attendere il completamento di tutte queste funzioni prima dell’esecuzione, si consiglia di tenere un conteggio semplice di quante funzioni sono state completate:

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

    Eri molto vicino, ma dovresti passare la chiusura per get invece di metterlo nella callback:

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

    So che questo è un vecchio thread, ma in ogni caso aggiungendo la mia risposta. La funzione ES2015 ha la funzione di rebinding della variabile loop su ogni iterazione, quindi mantiene il valore della variabile loop in callback asincroni, quindi puoi provare la seguente:

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

    Tuttavia, è preferibile utilizzare forEach o creare una chiusura utilizzando la funzione invocato immediatamente, poiché let è la funzionalità ES2015 e potrebbe non supportare tutti i browser e le implementazioni. Da qui sotto Bindings ->let->for/for-in loop iteration scope posso vedere che non è supportato fino a Edge 13 e nemmeno fino a Firefox 49 (non ho controllato questi browser). Dice anche che non è supportato con il Nodo 4, ma ho provato personalmente e sembra supportato.

    Prova questo, usando la 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() }) }) } })() 

    Ciò interromperà il ciclo in ogni ciclo finché non viene triggersta la funzione next()

    ES2017: puoi racchiudere il codice asincrono all’interno di una funzione (ad esempio XHRPost) restituendo una promise (codice asincrono all’interno della promise).

    Quindi chiama la funzione (XHRPost) all’interno del ciclo for ma con la magica parola chiave Await. 🙂

     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 si desidera eseguire funzioni asincrone all’interno di un ciclo, ma si desidera mantenere l’indice o altre variabili dopo l’esecuzione di un callback, è ansible avvolgere il codice in un IIFE (espressione di funzione immediatamente richiamata).

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

    Usando ES6 (typescript) puoi sfruttare i vantaggi di 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();