2012-11-12 10 views
37

Ich habe den folgenden Code:Aufruf eine asynchrone Funktion innerhalb eines for-Schleife in JavaScript

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

mc_cli ist eine Verbindung zu einer Memcached-Datenbank. Wie Sie sich vorstellen können, ist die Callback-Funktion asynchron und kann daher ausgeführt werden, wenn die for-Schleife bereits beendet wurde. Wenn Sie auf diese Weise do_something(i) aufrufen, wird immer der letzte Wert der for-Schleife verwendet.

Ich habe versucht, mit einem Verschluss auf diese Weise

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

aber anscheinend diese wieder verwendet immer den letzten Wert des Index der for-Schleife.

Ich versuchte auch, wie so eine Funktion vor dem for-Schleife erklärt:

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

und dann

do_something(create_closure(i)()) 

Aufruf aber wieder ohne Erfolg, mit dem Rückgabewert immer der letzte Wert des Wesen für die Schleife.

Kann mir jemand sagen, was mache ich falsch mit Schließungen? Ich dachte, ich hätte sie verstanden, aber ich kann mir nicht vorstellen, warum das nicht funktioniert.

Antwort

68

Da Sie ein Array durchlaufen, können Sie einfach forEach verwenden, die das Listenelement und den Index im Rückruf bereitstellt. Die Iteration wird ihren eigenen Umfang haben.

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

vielen Dank ausgelöst Kumpel stoppen! Dieser Code hat mich gerettet !!! : D – DDave

+0

@joseph du Verstand klingt großartig. Können Sie mir diesen Teil von mir erklären? "Iteration wird einen eigenen Umfang haben"? –

12

Sie waren ziemlich nah dran, aber man sollte die Schließung zu get anstatt, es in dem Callback übergeben:

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


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

Danke, es funktionierte genauso gut wie die, die ich als richtig markiert habe, aber ich habe stattdessen diese Lösung verwendet. Wie auch immer vielen Dank! – Masiar

36

Dies ist die asynchrone-Funktion-inside-a-Loop-Paradigma und Ich behandle es normalerweise mit einer sofort aufgerufenen-anonymen Funktion. Dadurch wird sichergestellt, dass die asynchronen Funktionen mit dem korrekten Wert der Indexvariablen aufgerufen werden.

Okay, großartig. Also wurden alle asynchronen Funktionen gestartet und die Schleife beendet. Jetzt ist nicht zu sagen, wann diese Funktionen aufgrund ihrer asynchronen Natur oder in welcher Reihenfolge abgeschlossen werden. Wenn Sie Code haben, alle warten muss, bis diese Funktionen vor der Ausführung abgeschlossen haben, ich empfehlen Ihnen, eine einfache zählen, wie viele Funktionen beendet haben:

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

Es ist schon eine Weile her, aber danke dafür. Ich löste ein großes Problem, das ich auf eine wirklich einfache Art hatte. – Raelshark

+0

Gute Lösung. Danken. – mile

+1

Vielen Dank für diesen Namen: asynchron-function-inside-a-loop :) – compte14031879

7

Ich weiß, dass dies ein alter Thread ist aber trotzdem meine Antwort hinzufügen. let ES2015 hat die Funktion, um die Schleifenvariable bei jeder Iteration von rebinding, so behält sie den Wert der Schleifenvariable in asynchronen Rückrufen, so können Sie versuchen, die unter einem:

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

Aber wie auch immer, es ist besser forEach zu verwenden oder Erstellen Sie eine Schließung mit sofort aufgerufenen Funktion, da let ist ES2015-Funktion und möglicherweise nicht alle Browser und Implementierungen unterstützen. Von here unter Bindings ->let->for/for-in loop iteration scope kann ich sehen, es wird nicht unterstützt bis Edge 13 und sogar bis Firefox 49 (Ich habe nicht in diesen Browsern überprüft).Es sagt sogar, dass es nicht mit Knoten 4 unterstützt wird, aber ich persönlich getestet und es scheint, dass es unterstützt wird.

+0

Ich habe seit 24 Stunden meinen Kopf an eine Wand geschlagen, weil ich nicht herausfinden konnte, warum die f ** king 'for loop' nicht so funktioniert, wie sie es sich vorstellt. Ich habe die ganze Zeit 'var i = 0' benutzt, bis ich deinen Post sehe. Ich ändere 'var i = 0' zu' let i = 0' und alles funktioniert wunderbar. Wie kann ich dir meinen ganzen Ruf geben, du verdienst das alles ... –

+0

@TalhaTemuri haha, gut, dass es dir geholfen hat. – vikneshwar

0

Wenn Sie asynchrone Funktionen innerhalb einer Schleife ausführen möchten, aber den Index oder andere Variablen behalten möchten, nachdem ein Callback ausgeführt wurde, können Sie den Code in einen IIFE (sofort aufgerufenen Funktionsausdruck) umbrechen.

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

die Sie interessieren, die async/await Syntax und Promise mit

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

Dadurch wird die Schleife in jedem Zyklus, bis die next() Funktion

Verwandte Themen