2012-11-12 17 views
37

Mam następujący kod:Wywołanie asynchroniczne funkcji w pętli for w JavaScript

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

mc_cli jest połączenie z bazą danych memcached. Jak można sobie wyobrazić, funkcja zwrotna jest asynchroniczna, dlatego może być wykonana, gdy pętla for już się zakończyła. Ponadto, wywołując w ten sposób do_something(i) zawsze używa ostatniej wartości pętli for.

Próbowałem z zamknięciem w ten sposób

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

ale widocznie to jest zawsze ponownie stosując ostatnią wartość indeksu pętli for.

Próbowałem też deklarowania funkcji przed pętli tak:

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

a następnie wywołanie

do_something(create_closure(i)()) 

ale znowu bez powodzenia, o wartości powrót zawsze jest ostatnia wartość z dla pętli.

Czy ktoś może mi powiedzieć, co robię źle z zamknięciami? Myślałem, że je rozumiem, ale nie rozumiem, dlaczego to nie działa.

Odpowiedz

68

Ponieważ używasz tablicy, możesz po prostu użyć forEach, która zapewnia element listy i indeks wywołania zwrotnego. Iteracja będzie miała swój własny zakres.

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

dużo dzięki kolego! ten kod mnie uratował !!! : D – DDave

+0

@joseph ci rozumowanie brzmi świetnie. Czy możesz wyjaśnić tę część mnie proszę "Iteracja będzie miała swój własny zakres"? –

12

Byłaś całkiem blisko, ale należy zdać zamknięcie do get zamiast umieszczenie go wewnątrz 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

Dzięki, działało tak dobrze, jak to, które zaznaczyłem jako poprawne, ale użyłem tego rozwiązania zamiast tego. W każdym razie wielkie dzięki! – Masiar

36

To asynchroniczny-funkcja-wewnątrz-a-loop paradygmat, i Zwykle radzę sobie z tym za pomocą funkcji anonimowo wywołanej. Zapewnia to wywołanie funkcji asynchronicznych z poprawną wartością zmiennej indeksowej.

Okay, świetnie. Wszystkie funkcje asynchroniczne zostały uruchomione, a pętla zostaje zamknięta. Teraz nie wiadomo, kiedy te funkcje się zakończą, ze względu na ich asynchroniczny charakter lub kolejność ich wykonywania. Jeśli masz kod, który musi czekać, aż wszystkie te funkcje zostały zakończone przed wykonaniem, polecam zachowaniu prostej rachubę, ile funkcje zakończeniu:

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

Minęło trochę czasu, ale dziękuję za to. Rozwiązałem duży problem, który miałem w bardzo prosty sposób. – Raelshark

+0

Dobre rozwiązanie. Podziękować. – mile

+1

Dziękujemy za wpisanie nazwy: asynchroniczna funkcja-wewnątrz-pętli :) – compte14031879

7

Wiem, że to stary wątek, ale mimo to dodanie moją odpowiedź. ES2015 let posiada funkcję ponownego wiązania zmiennej pętli na każdej iteracji, dzięki czemu utrzymuje wartość zmiennej pętli w asynchronicznych wywołań zwrotnych, więc można spróbować poniżej jeden:

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

Ale tak czy inaczej, to lepiej użyć forEach lub Utwórz zamknięcie za pomocą funkcji natychmiast wywoływanej, ponieważ let jest funkcją ES2015 i może nie obsługiwać wszystkich przeglądarek i implementacji. Od here pod Bindings ->let->for/for-in loop iteration scope widzę, że nie jest obsługiwany do Edge 13 i nawet do Firefox 49 (Nie sprawdziłem w tych przeglądarkach).Mówi nawet, że nie jest obsługiwany przez Node 4, ale ja osobiście przetestowałem i wydaje się, że jest on obsługiwany.

+0

Uderzyłem głową o ścianę przez ostatnie 24 godziny, ponieważ nie mogłem się domyślić, dlaczego f ** king 'for loop' nie działa tak jak powinien. Używałem 'var i = 0' przez cały ten czas, dopóki nie zobaczę twojego postu. Zmieniam 'var i = 0' na' let i = 0', a wszystko w magiczny sposób działa poprawnie. Jak mogę dać ci całą moją reputację, zasługujesz na to wszystko ... –

+0

@TalhaTemuri haha, dobrze, że pomógł ci. – vikneshwar

0

Jeśli chcesz uruchomić asynchroniczne funkcje w pętli, ale nadal chcesz zachować indeks lub inne zmienne po wykonaniu wywołania zwrotnego, możesz zawinąć swój kod w IIFE (natychmiastowe wywołanie funkcji).

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

Spróbuj tego, używając składni async/await i 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() 
      }) 
     }) 
    } 
})() 

Pozwoli to zatrzymać pętlę w każdym cyklu, aż funkcja next() jest wyzwalany