2012-11-12 7 views
37

ho il seguente codice:chiama una funzione asincrona all'interno di un ciclo for in JavaScript

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

mc_cli è una connessione ad 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) utilizza sempre l'ultimo valore del ciclo for.

ho provato con una chiusura in questo modo

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

ma pare che questo è di nuovo utilizzando sempre l'ultimo valore dell'indice del ciclo for.

Ho provato anche dichiarare una funzione prima che il ciclo for in questo modo:

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

e quindi chiamando

do_something(create_closure(i)()) 

ma ancora una volta senza successo, con il valore di ritorno di essere sempre l'ultimo valore della per ciclo.

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

risposta

68

Poiché si sta eseguendo un array, è sufficiente utilizzare forEach che fornisce l'elemento 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); 
    }); 
}); 
+1

sacco di grazie amico! questo codice mi ha salvato !!! : D – DDave

+0

@ joseph il tuo ragionamento suona alla grande. Puoi spiegare questa parte di me per favore "Iterazione avrà il suo scopo"? –

12

Si erano abbastanza vicino, ma si dovrebbe passare la chiusura al get invece di mettere dentro il callback:

function createCallback(i) { 
    return function(){ 
     do_something(i); 
    } 
} 


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

Grazie, ha funzionato bene come quello che ho contrassegnato come corretto, ma ho usato quella soluzione invece di questa. Comunque grazie mille! – Masiar

36

Questa è la asincrono-function-dentro-a-loop di paradigma, e Di solito mi occupo di esso 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 ha bisogno di aspettare fino a quando tutte queste funzioni hanno completato prima di eseguire, vi consiglio di mantenere un semplice conteggio di quante funzioni hanno terminato:

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

È passato un po 'di tempo, ma grazie per questo. Risolto un grosso problema che stavo vivendo in un modo molto semplice. – Raelshark

+0

Buona soluzione. Grazie. – mile

+1

Grazie per aver messo un nome su questo: asincrono-function-inside-a-loop :) – compte14031879

7

So che questo è un filo vecchio, ma in ogni caso l'aggiunta di mia risposta. ES2015 let ha la caratteristica di rebinding l'indice del ciclo ad ogni iterazione, quindi mantiene il valore della variabile del ciclo di callback asincroni, in modo da poter provare il di sotto di un:

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

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

+0

Ho sbattuto la testa su un muro per 24 ore, perché non riuscivo a capire perché il f ** king 'for loop' non funzioni come suppone. Sto usando 'var i = 0' per tutto questo tempo fino a quando vedo il tuo post. Cambio 'var i = 0' in' let i = 0' e tutto funziona magicamente. Come posso darti tutta la mia reputazione, te lo meriti tutto ... –

+0

@TalhaTemuri haha, bene che ti ha aiutato. – vikneshwar

0

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, è possibile 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); 
0

Prova questa, utilizzando la sintassi e async/awaitPromise

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

Questo fermerà il ciclo in ogni ciclo fino a quando la funzione viene attivata next()

Problemi correlati