914 Stimmen

Warum bleibt meine Variable unverändert, nachdem ich sie innerhalb einer Funktion modifiziere? - Asynchroner Code-Verweis

Bei den folgenden Beispielen, warum ist outerScopeVar in allen Fällen undefiniert?

var outerScopeVar;

var img = document.createElement('img');
img.onload = function() {
    outerScopeVar = this.width;
};
img.src = 'lolcat.png';
alert(outerScopeVar);

var outerScopeVar;
setTimeout(function() {
    outerScopeVar = 'Hallo asynchrone Welt!';
}, 0);
alert(outerScopeVar);

// Beispiel mit jQuery
var outerScopeVar;
$.post('loldog', function(response) {
    outerScopeVar = response;
});
alert(outerScopeVar);

// Node.js Beispiel
var outerScopeVar;
fs.readFile('./catdog.html', function(err, data) {
    outerScopeVar = data;
});
console.log(outerScopeVar);

// mit Promises
var outerScopeVar;
myPromise.then(function (response) {
    outerScopeVar = response;
});
console.log(outerScopeVar);

// mit Observables
var outerScopeVar;
myObservable.subscribe(function (value) {
    outerScopeVar = value;
});
console.log(outerScopeVar);

// Geolocation-API
var outerScopeVar;
navigator.geolocation.getCurrentPosition(function (pos) {
    outerScopeVar = pos;
});
console.log(outerScopeVar);

Warum gibt es in all diesen Beispielen undefined aus? Ich möchte keine Workarounds, ich möchte wissen warum dies passiert.


Hinweis: Dies ist eine Kanonische Frage zur JavaScript-Asynchronität. Fühlen Sie sich frei, diese Frage zu verbessern und weitere vereinfachte Beispiele hinzuzufügen, mit denen die Community identifizieren kann.

192voto

Matt Punkte 72534

Die Antwort von Fabrício ist genau richtig; aber ich wollte seine Antwort ergänzen mit etwas weniger Technischem, das sich auf eine Analogie konzentriert, um das Konzept der Asynchronität zu erklären.


Eine Analogie...

Gestern benötigte die Arbeit, die ich machte, einige Informationen von einem Kollegen. Ich rief ihn an; hier ist, wie das Gespräch verlief:

Ich: Hi Bob, ich muss wissen, wie wir letzte Woche das foo gemacht haben. Jim möchte dazu einen Bericht, und du bist der einzige, der die Details dazu kennt.

Bob: Klar, aber es wird ca. 30 Minuten dauern?

Ich: Das ist großartig Bob. Ruf mich zurück, wenn du die Informationen hast!

An diesem Punkt legte ich auf. Da ich Informationen von Bob benötigte, um meinen Bericht fertigzustellen, ließ ich den Bericht zurück und machte stattdessen eine Kaffeepause, dann holte ich etwas E-Mails nach. 40 Minuten später (Bob ist langsam) rief Bob zurück und gab mir die Informationen, die ich brauchte. Zu diesem Zeitpunkt setzte ich meine Arbeit mit meinem Bericht fort, da ich alle Informationen hatte, die ich brauchte.


Stell dir vor, das Gespräch hätte stattdessen so stattgefunden:

Ich: Hi Bob, ich muss wissen, wie wir letzte Woche das foo gemacht haben. Jim möchte dazu einen Bericht, und du bist der einzige, der die Details dazu kennt.

Bob: Klar, aber es wird ca. 30 Minuten dauern?

Ich: Das ist großartig Bob. Ich werde warten.

Und ich saß da und wartete. Und wartete. Und wartete. 40 Minuten lang. Ich tat nichts als zu warten. Schließlich gab mir Bob die Informationen, wir legten auf, und ich beendete meinen Bericht. Aber ich hatte 40 Minuten Produktivität verloren.


Dies ist asynchrones vs. synchrone Verhalten

Genau das passiert in allen Beispielen in unserer Frage. Das Laden einer Grafik, das Laden einer Datei von der Festplatte und das Anfordern einer Seite über AJAX sind alles langsame Operationen (im Kontext der modernen Informatik).

Anstatt auf den Abschluss dieser langsamjährigen Operationen zu warten, ermöglicht es JavaScript, eine Rückruffunktion zu registrieren, die ausgeführt wird, wenn die langsame Operation abgeschlossen ist. In der Zwischenzeit wird jedoch JavaScript weiterhin anderen Code ausführen. Die Tatsache, dass JavaScript anderen Code ausführt, während es auf den Abschluss der langsamjährigen Operation wartet, macht das Verhaltenasynchron. Hätte JavaScript auf die Fertigstellung der Operation gewartet, bevor es anderen Code ausgeführt hätte, wäre dies einsynchrone Verhalten gewesen.

var outerScopeVar;    
var img = document.createElement('img');

// Hier registrieren wir die Rückruffunktion.
img.onload = function() {
    // Code innerhalb dieser Funktion wird ausgeführt, sobald das Bild geladen ist.
    outerScopeVar = this.width;
};

// Aber während das Bild geladen wird, fährt JavaScript fort mit der
// Bearbeitung der folgenden JavaScript-Zeilen.
img.src = 'lolcat.png';
alert(outerScopeVar);

In dem obigen Code bitten wir JavaScript, lolcat.png zu laden, was eine lange Operation ist. Die Rückruffunktion wird ausgeführt, sobald diese langsame Operation abgeschlossen ist, aber in der Zwischenzeit wird JavaScript weiterhin den nächsten Code verarbeiten; z.B. alert(outerScopeVar).

Deshalb sehen wir die Meldung undefined; da das alert() sofort verarbeitet wird, anstatt nachdem das Bild geladen wurde.

Um unseren Code zu reparieren, müssen wir einfach den Code alert(outerScopeVar) in die Rückruffunktion verschieben. Dadurch müssen wir die Variable outerScopeVar nicht mehr als globale Variable deklarieren.

var img = document.createElement('img');

img.onload = function() {
    var localScopeVar = this.width;
    alert(localScopeVar);
};

img.src = 'lolcat.png';

Es wird immer eine Rückruffunktion als Funktion angegeben, denn das ist der einzige* Weg in JavaScript, um etwas Code zu definieren, aber erst später auszuführen.

Deshalb ist in all unseren Beispielen die function() { /* Etwas machen */ } die Rückruffunktion; um alle Beispiele zu reparieren, müssen wir einfach den Code, der auf die Antwort der Operation wartet, dorthin verschieben!

* Technisch gesehen kann man auch eval() verwenden, aber eval() ist böse für diesen Zweck


Wie halte ich meinen Aufrufer wartend?

Möglicherweise haben Sie derzeit einen ähnlichen Code wie diesen;

function getWidthOfImage(src) {
    var outerScopeVar;

    var img = document.createElement('img');
    img.onload = function() {
        outerScopeVar = this.width;
    };
    img.src = src;
    return outerScopeVar;
}

var width = getWidthOfImage('lolcat.png');
alert(width);

Wir wissen jedoch jetzt, dass das return outerScopeVar sofort passiert; bevor die onload Rückruffunktion die Variable aktualisiert hat. Dies führt dazu, dass getWidthOfImage() undefined zurückgibt und undefined angezeigt wird.

Um dies zu beheben, müssen wir der Funktion, die getWidthOfImage() aufruft, ermöglichen, eine Rückruffunktion zu registrieren, und dann das Alerten der Breite in diese Rückruffunktion verschieben;

function getWidthOfImage(src, cb) {     
    var img = document.createElement('img');
    img.onload = function() {
        cb(this.width);
    };
    img.src = src;
}

getWidthOfImage('lolcat.png', function (width) {
    alert(width);
});

... wie zuvor beachten Sie, dass wir die globalen Variablen (in diesem Fall width) entfernen konnten.

97voto

JohnnyHK Punkte 289697

Hier ist eine präzisere Antwort für Personen, die eine schnelle Referenz suchen, sowie einige Beispiele zur Verwendung von Promises und async/await.

Beginnen Sie mit dem naiven Ansatz (der nicht funktioniert) für eine Funktion, die eine asynchrone Methode aufruft (in diesem Fall setTimeout) und eine Nachricht zurückgibt:

function getMessage () {
  var outerScopeVar;
  setTimeout(function() {
    outerScopeVar = 'Hallo asynchrone Welt!';
  }, 0);
  return outerScopeVar;
}
console.log(getMessage ());

undefined wird in diesem Fall protokolliert, weil getMessage zurückgegeben wird, bevor der setTimeout Rückruf aufgerufen wird und outerScopeVar aktualisiert.

Die zwei Hauptwege, um dies zu lösen, sind die Verwendung von Callbacks und Promises :

Callbacks

Die Änderung hier besteht darin, dass getMessage einen callback Parameter akzeptiert, der aufgerufen wird, um die Ergebnisse an den Aufrufer zurückzugeben, sobald verfügbar.

function getMessage (callback) {
  setTimeout(function() {
    callback('Hallo asynchrone Welt!');
  }, 0);
}
getMessage(function(message) {
  console.log(message);
});

Promises

Promises bieten eine Alternative, die flexibler als Callbacks ist, da sie natürlicherweise kombiniert werden können, um mehrere asynchrone Operationen zu koordinieren. Eine Promises/A+ Standardimplementierung ist nativ in node.js (0.12+) und vielen aktuellen Browsern vorhanden, aber auch in Bibliotheken wie Bluebird und Q implementiert.

function getMessage () {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve('Hallo asynchrone Welt!');
    }, 0);
  });
}

getMessage().then(function(message) {
  console.log(message);  
});

jQuery Deferreds

jQuery bietet Funktionen, die Promises ähnlich sind, mit seinen Deferreds.

function getMessage () {
  var deferred = $.Deferred();
  setTimeout(function() {
    deferred.resolve('Hallo asynchrone Welt!');
  }, 0);
  return deferred.promise();
}

getMessage().done(function(message) {
  console.log(message);  
});

async/await

Wenn Ihre JavaScript-Umgebung die Unterstützung für async und await (wie Node.js 7.6+) einschließt, können Sie Promises synchron innerhalb von async-Funktionen verwenden:

function getMessage () {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve('Hallo asynchrone Welt!');
        }, 0);
    });
}

async function main () {
    let message = await getMessage();
    console.log(message);
}

main();

61voto

Johannes Fahrenkrug Punkte 40264

Um das Offensichtliche zu sagen, repräsentiert die Tasse outerScopeVar.

Asynchrone Funktionen sind so...

asynchroner Aufruf für Kaffee

16voto

Teja Punkte 1226

Die anderen Antworten sind ausgezeichnet und ich möchte einfach eine klare Antwort darauf geben. Beschränke dich einfach auf jQuery-Asynchronaufrufe

Alle Ajax-Aufrufe (einschließlich des $.get oder $.post oder $.ajax) sind asynchron.

Unter Berücksichtigung deines Beispiels

var outerScopeVar; //Zeile 1
$.post('loldog', function(response) { //Zeile 2
    outerScopeVar = response;
});
alert(outerScopeVar); //Zeile 3

Die Codeausführung beginnt in Zeile 1, deklariert die Variable und ruft in Zeile 2 einen asynchronen Aufruf auf (d.h. die Post-Anfrage) und setzt die Ausführung in Zeile 3 fort, ohne auf die vollständige Ausführung der Post-Anfrage zu warten.

Angenommen, die Post-Anfrage dauert 10 Sekunden, der Wert von outerScopeVar wird erst nach diesen 10 Sekunden gesetzt.

Um es auszuprobieren,

var outerScopeVar; //Zeile 1
$.post('loldog', function(response) { //Zeile 2, dauert 10 Sekunden, um abgeschlossen zu werden
    outerScopeVar = response;
});
alert("Lass uns hier eine Weile warten! Warten macht Spaß"); //Zeile 3
alert(outerScopeVar); //Zeile 4

Wenn du das jetzt ausführst, erhältst du einen Alert in Zeile 3. Warte nun eine Weile, bis du sicher bist, dass die Post-Anfrage einen Wert zurückgegeben hat. Dann, wenn du auf OK klickst, im Alert-Fenster wird der nächste Alert den erwarteten Wert ausgeben, weil du darauf gewartet hast.

In einem realen Szenario sieht der Code wie folgt aus,

var outerScopeVar;
$.post('loldog', function(response) {
    outerScopeVar = response;
    alert(outerScopeVar);
});

Der gesamte Code, der von den asynchronen Aufrufen abhängt, wird in den asynchronen Block verschoben oder durch Warten auf die asynchronen Aufrufe realisiert.

12voto

Tom Sebastian Punkte 3263

In all these scenarios outerScopeVar is modified or assigned a value asynchronously or happening in a later time(waiting or listening for some event to occur),for which the current execution will not wait.So all these cases current execution flow results in outerScopeVar = undefined

Lassen Sie uns jeden Beispiel diskutieren (Ich habe den Abschnitt markiert, der asynchron oder verzögert für das Auftreten von Ereignissen aufgerufen wird):

1.

enter image description here

Hier registrieren wir einen Eventlistener, der bei diesem bestimmten Ereignis ausgeführt wird. Hier das Laden des Bildes. Dann setzt die aktuelle Ausführung mit den nächsten Zeilen img.src = 'lolcat.png'; und alert(outerScopeVar); fort, während das Ereignis möglicherweise nicht auftritt. Die Funktion img.onload wartet darauf, dass das referenzierte Bild asynchron geladen wird. Dies wird auch in den folgenden Beispielen geschehen - das Ereignis kann variieren.

2.

2

Hier spielt das Timeout-Ereignis eine Rolle, das den Handler nach der angegebenen Zeit aufrufen wird. Hier ist es 0, aber es registriert trotzdem ein asynchrones Ereignis, das am Ende der Event Queue zur Ausführung hinzugefügt wird und somit die garantierte Verzögerung gewährleistet.

3.

enter image description here Dieses Mal ein Ajax-Rückruf.

4.

enter image description here

Node kann als eine Art von asynchronem Coding betrachtet werden. Hier wird die markierte Funktion als Callback-Handler registriert, der nach dem Lesen der spezifizierten Datei ausgeführt wird.

5.

enter image description here

Offensichtlich ist ein Versprechen (etwas wird in Zukunft getan) asynchron. siehe Was sind die Unterschiede zwischen Deferred, Promise und Future in JavaScript?

https://www.quora.com/Whats-the-difference-between-a-promise-and-a-callback-in-Javascript

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X