it-swarm.com.de

So strukturieren Sie JavaScript-Rückrufe, damit der Funktionsumfang ordnungsgemäß verwaltet wird

Ich verwende XMLHttpRequest, und ich möchte auf eine lokale Variable in der Erfolgsrückruffunktion zugreifen.

Hier ist der Code:

function getFileContents(filePath, callbackFn) {  
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (xhr.readyState == 4) {
            callbackFn(xhr.responseText);
        }
    }
    xhr.open("GET", chrome.extension.getURL(filePath), true);
    xhr.send();
}

Und ich möchte es so nennen:

var test = "lol";

getFileContents("hello.js", function(data) {
    alert(test);
});

Hier wäre test außerhalb des Gültigkeitsbereichs der Callback-Funktion, da nur die Variablen der umgebenden Funktion innerhalb der Callback-Funktion zugänglich sind. Was ist der beste Weg, um test an die Rückruffunktion zu übergeben, damit alert(test);test korrekt anzeigt?

Bearbeiten: 

Wenn ich nun den folgenden Code habe, der die oben definierte Funktion aufruft:

for (var test in testers) {
    getFileContents("hello.js", function(data) {
        alert(test);
    });
}

Der alert(test);-Code gibt nur den letzten Wert von test aus der for-Schleife aus. Wie mache ich es so, dass es den Wert von test während der Zeit ausgibt, zu der die Funktion getFileContents aufgerufen wurde? (Ich würde dies gerne tun, ohne getFileContents zu ändern, da dies eine sehr allgemeine Hilfsfunktion ist und ich sie nicht spezifisch machen möchte, indem eine bestimmte Variable wie test an sie übergeben wird.

32
Chetan

Mit dem von Ihnen angegebenen Code befindet sich test weiterhin innerhalb des Callbacks. xhr wird nicht anders als xhr.responseText als data übergeben.

Vom Kommentar aktualisiert :

Angenommen, Ihr Code sieht ungefähr so ​​aus:

for (var test in testers)
  getFileContents("hello"+test+".js", function(data) {
    alert(test);
  });
}

Während dieses Skript ausgeführt wird, wird test die Werte der Schlüssel in testers zugewiesen. getFileContents wird jedes Mal aufgerufen, wodurch eine Anforderung im Hintergrund gestartet wird. Wenn die Anforderung abgeschlossen ist, ruft sie den Rückruf auf. test enthält den FINAL VALUE aus der Schleife, da die Ausführung der Schleife bereits abgeschlossen ist. 

Es gibt eine Technik, die Sie verwenden können, die Schließung genannt wird, um diese Art von Problem zu beheben. Sie können eine Funktion erstellen, die Ihre Callback-Funktion zurückgibt, und einen neuen Gültigkeitsbereich erstellen, den Sie für Ihre Variablen verwenden können:

for (var test in testers) {
  getFileContents("hello"+test+".js", 
    (function(test) { // lets create a function who has a single argument "test"
      // inside this function test will refer to the functions argument
      return function(data) {
        // test still refers to the closure functions argument
        alert(test);
      };
    })(test) // immediately call the closure with the current value of test
  );
}

Dadurch wird (zusammen mit unserer neuen Funktion) ein neuer Gültigkeitsbereich erstellt, der den Wert von test "beibehält". 

Eine andere Art, das Gleiche zu schreiben:

for (var test in testers) {
  (function(test) { // lets create a function who has a single argument "test"
    // inside this function test will refer to the functions argument
    // not the var test from the loop above
    getFileContents("hello"+test+".js", function(data) {
        // test still refers to the closure functions argument
        alert(test);
    });
  })(test); // immediately call the closure with the value of `test` from `testers`
}
40
gnarf

JavaScript verwendet lexikalisches Scoping , was im Wesentlichen bedeutet, dass Ihr zweites Codebeispiel genauso funktioniert, wie Sie es beabsichtigen. 

Betrachten Sie das folgende Beispiel, das von David Flanagans Definitive Guide entlehnt wurde.1:

var x = "global";

function f() {
  var x = "local";
  function g() { alert(x); }
  g();
}

f();  // Calling this function displays "local"

Beachten Sie außerdem, dass JavaScript im Gegensatz zu C, C++ und Java keinen Gültigkeitsbereich auf Blockebene hat.

Darüber hinaus können Sie auch den folgenden Artikel lesen, den ich sehr empfehle:


1 David Flanagan: JavaScript - The Definitive Guide , vierte Ausgabe, Seite 48.

7
Daniel Vassallo

In diesem Szenario wird der Test wie erwartet gelöst, der Wert von this kann jedoch anders sein. Um den Gültigkeitsbereich beizubehalten, würden Sie es normalerweise als Parameter für die asynchrone Funktion definieren:

function getFileContents(filePath, callbackFn, scope) {  
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (xhr.readyState == 4) {
            callbackFn.call(scope, xhr.responseText);
        }
    }
    xhr.open("GET", chrome.extension.getURL(filePath), true);
    xhr.send();
}


//then to call it:
var test = "lol";

getFileContents("hello.js", function(data) {
    alert(test);
}, this);
2
Igor Zevaka

Ich bin auf ein ähnliches Problem gestoßen. Mein Code sah so aus:

for (var i=0; i<textFilesObj.length; i++)
{
    var xmlHttp=new XMLHttpRequest();
    var name = "compile/" + textFilesObj[i].fileName;
    var content = textFilesObj[i].content;

    xmlHttp.onreadystatechange=function()
    {
        if(xmlHttp.readyState==4)
        {
            var responseText = xmlHttp.responseText;
            Debug(responseText);
        }
    }

    xmlHttp.open("POST","save1.php",true);
    xmlHttp.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
    xmlHttp.send("filename="+name+"&text="+encodeURIComponent(content));
}

Die Ausgabe war oft der Antworttext des letzten Objekts (aber nicht immer). In der Tat war es irgendwie zufällig und sogar die Anzahl der Antworten variierte (für eine konstante Eingabe). Es stellt sich heraus, dass die Funktion geschrieben worden sein sollte:

    xmlHttp.onreadystatechange=function()
    {
        if(this.readyState==4)
        {
            var responseText = this.responseText;
            Debug(responseText);
        }
    }

Grundsätzlich wird in der ersten Version das "xmlHttp" -Objekt auf die Version in der aktuellen Stufe der Schleife gesetzt. Meistens wäre die Schleife beendet, bevor eine der AJAX -Anforderungen abgeschlossen wurde. In diesem Fall bezog sich "xmlHttp" auf die letzte Instanz der Schleife. Wenn diese letzte Anforderung vor den anderen Anforderungen beendet wurde, würden sie alle die Antwort der letzten Anforderung ausdrucken, wenn sich ihr Bereitschaftsstatus geändert hat (selbst wenn deren Bereitschaftsstatus <4 war). 

Die Lösung besteht darin, "xmlHttp" durch "this" zu ersetzen, sodass die innere Funktion bei jedem Aufruf des Callbacks auf die richtige Instanz des Anforderungsobjekts verweist.

0
Bruce