Warum ist anhand der folgenden Beispiele 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 = 'Hello Asynchronous World!';
}, 0);
alert(outerScopeVar);
// Example using some jQuery
var outerScopeVar;
$.post('loldog', function(response) {
outerScopeVar = response;
});
alert(outerScopeVar);
// Node.js example
var outerScopeVar;
fs.readFile('./catdog.html', function(err, data) {
outerScopeVar = data;
});
console.log(outerScopeVar);
// with promises
var outerScopeVar;
myPromise.then(function (response) {
outerScopeVar = response;
});
console.log(outerScopeVar);
// geolocation API
var outerScopeVar;
navigator.geolocation.getCurrentPosition(function (pos) {
outerScopeVar = pos;
});
console.log(outerScopeVar);
Warum wird undefined
in all diesen Beispielen ausgegeben ? Ich möchte keine Problemumgehungen, ich möchte wissen, warum dies geschieht.
Hinweis: Dies ist eine kanonische Frage zur JavaScript-Asynchronität . Sie können diese Frage gerne verbessern und vereinfachte Beispiele hinzufügen, mit denen sich die Community identifizieren kann.
javascript
asynchronous
Fabrício Matté
quelle
quelle
Antworten:
Ein Wort Antwort: Asynchronität .
Vorworte
Dieses Thema wurde hier in Stack Overflow mindestens ein paar tausend Mal wiederholt. Daher möchte ich zunächst auf einige äußerst nützliche Ressourcen hinweisen:
Antwort von @Felix Kling auf "Wie kann ich die Antwort von einem asynchronen Anruf zurückgeben?" . Siehe seine ausgezeichnete Antwort zur Erläuterung synchroner und asynchroner Abläufe sowie den Abschnitt "Restrukturierungscode".
@Benjamin Gruenbaum hat sich auch viel Mühe gegeben, die Asynchronität im selben Thread zu erklären.
Die Antwort von @Matt Esch auf "Daten aus fs.readFile abrufen" erklärt auch die Asynchronität auf einfache Weise sehr gut.
Die Antwort auf die vorliegende Frage
Lassen Sie uns zuerst das allgemeine Verhalten verfolgen. In allen Beispielen wird das
outerScopeVar
innerhalb einer Funktion geändert . Diese Funktion wird eindeutig nicht sofort ausgeführt, sondern zugewiesen oder als Argument übergeben. Das nennen wir einen Rückruf .Die Frage ist nun, wann dieser Rückruf aufgerufen wird.
Das hängt vom Fall ab. Versuchen wir noch einmal, ein häufiges Verhalten zu verfolgen:
img.onload
kann irgendwann in der Zukunft aufgerufen werden , wenn (und wenn) das Bild erfolgreich geladen wurde.setTimeout
kann irgendwann in der Zukunft aufgerufen werden , nachdem die Verzögerung abgelaufen ist und das Timeout nicht von abgebrochen wurdeclearTimeout
. Hinweis: Selbst bei Verwendung0
als Verzögerung haben alle Browser eine minimale Zeitüberschreitungsverzögerung (in der HTML5-Spezifikation auf 4 ms festgelegt).$.post
Der Rückruf von jQuery wird möglicherweise irgendwann in der Zukunft aufgerufen , wenn (und wenn) die Ajax-Anforderung erfolgreich abgeschlossen wurde.fs.readFile
möglicherweise irgendwann in der Zukunft aufgerufen , wenn die Datei erfolgreich gelesen wurde oder ein Fehler aufgetreten ist.In allen Fällen haben wir einen Rückruf, der möglicherweise irgendwann in der Zukunft ausgeführt wird . Dies "irgendwann in der Zukunft" wird als asynchroner Fluss bezeichnet .
Die asynchrone Ausführung wird aus dem synchronen Fluss verdrängt. Das heißt, der asynchrone Code wird niemals ausgeführt, während der synchrone Codestapel ausgeführt wird. Dies ist die Bedeutung von JavaScript als Single-Thread.
Insbesondere wenn die JS-Engine inaktiv ist und keinen Stapel von (a) synchronem Code ausführt, werden Ereignisse abgefragt, die möglicherweise asynchrone Rückrufe ausgelöst haben (z. B. abgelaufenes Zeitlimit, empfangene Netzwerkantwort), und diese nacheinander ausgeführt. Dies wird als Ereignisschleife angesehen .
Das heißt, der in den handgezeichneten roten Formen hervorgehobene asynchrone Code kann erst ausgeführt werden, nachdem der gesamte verbleibende synchrone Code in den jeweiligen Codeblöcken ausgeführt wurde:
Kurz gesagt, die Rückruffunktionen werden synchron erstellt, aber asynchron ausgeführt. Sie können sich nur dann auf die Ausführung einer asynchronen Funktion verlassen, wenn Sie wissen, dass sie ausgeführt wurde, und wie geht das?
Es ist wirklich einfach. Die Logik, die von der Ausführung der asynchronen Funktion abhängt, sollte innerhalb dieser asynchronen Funktion gestartet / aufgerufen werden. Wenn Sie beispielsweise
alert
s undconsole.log
s auch innerhalb der Rückruffunktion verschieben, wird das erwartete Ergebnis ausgegeben, da das Ergebnis zu diesem Zeitpunkt verfügbar ist.Implementierung Ihrer eigenen Rückruflogik
Oft müssen Sie mehr mit dem Ergebnis einer asynchronen Funktion tun oder verschiedene Dinge mit dem Ergebnis tun, je nachdem, wo die asynchrone Funktion aufgerufen wurde. Lassen Sie uns ein etwas komplexeres Beispiel angehen:
Hinweis: Ich verwende
setTimeout
mit einer zufälligen Verzögerung als eine allgemeine asynchrone Funktion gilt das gleiche Beispiel zu Ajax,readFile
,onload
und anderer asynchroner Strömung.Dieses Beispiel hat eindeutig das gleiche Problem wie die anderen Beispiele. Es wartet nicht, bis die asynchrone Funktion ausgeführt wird.
Lassen Sie uns ein eigenes Rückrufsystem implementieren. Zunächst einmal werden wir das Hässliche los,
outerScopeVar
das in diesem Fall völlig nutzlos ist. Dann fügen wir einen Parameter hinzu, der ein Funktionsargument akzeptiert, unseren Rückruf. Wenn die asynchrone Operation beendet ist, rufen wir diesen Rückruf auf und übergeben das Ergebnis. Die Implementierung (bitte lesen Sie die Kommentare in der Reihenfolge):Codefragment des obigen Beispiels:
In realen Anwendungsfällen stellen die DOM-API und die meisten Bibliotheken in den meisten Fällen bereits die Rückruffunktion bereit (die
helloCatAsync
Implementierung in diesem demonstrativen Beispiel). Sie müssen nur die Rückruffunktion übergeben und verstehen, dass sie außerhalb des synchronen Flusses ausgeführt wird, und Ihren Code neu strukturieren, um dies zu berücksichtigen.Sie werden auch feststellen, dass es aufgrund der asynchronen Natur unmöglich ist,
return
einen Wert von einem asynchronen Fluss zurück zu dem synchronen Fluss zu erhalten, in dem der Rückruf definiert wurde, da die asynchronen Rückrufe lange ausgeführt werden, nachdem der synchrone Code bereits ausgeführt wurde.Anstatt
return
einen Wert aus einem asynchronen Rückruf zu verwenden, müssen Sie das Rückrufmuster verwenden oder ... Versprechen.Versprechen
Obwohl es Möglichkeiten gibt, die Callback-Hölle mit Vanilla JS in Schach zu halten , werden Versprechen immer beliebter und werden derzeit in ES6 standardisiert (siehe Promise - MDN ).
Versprechen (auch bekannt als Futures) bieten ein lineareres und damit angenehmeres Lesen des asynchronen Codes, aber die Erklärung ihrer gesamten Funktionalität fällt nicht in den Geltungsbereich dieser Frage. Stattdessen überlasse ich diese hervorragenden Ressourcen den Interessierten:
Weitere Informationen zur JavaScript-Asynchronität
quelle
Fabrícios Antwort ist genau richtig; aber ich wollte seine Antwort mit etwas weniger Technischem ergänzen, das sich auf eine Analogie konzentriert, um das Konzept der Asynchronität zu erklären .
Eine Analogie ...
Gestern erforderte meine Arbeit einige Informationen von einem Kollegen. Ich habe ihn angerufen; So verlief das Gespräch:
Zu diesem Zeitpunkt legte ich auf. Da ich Informationen von Bob brauchte, um meinen Bericht zu vervollständigen, verließ ich den Bericht und ging stattdessen auf einen Kaffee, dann holte ich mir eine E-Mail. 40 Minuten später (Bob ist langsam) rief Bob zurück und gab mir die Informationen, die ich brauchte. Zu diesem Zeitpunkt nahm ich meine Arbeit mit meinem Bericht wieder auf, da ich alle Informationen hatte, die ich brauchte.
Stellen Sie sich vor, das Gespräch wäre stattdessen so verlaufen.
Und ich saß da und wartete. Und wartete. Und wartete. Für 40 Minuten. Nichts tun als warten. Schließlich gab Bob mir die Informationen, wir legten auf und ich vervollständigte meinen Bericht. Aber ich hatte 40 Minuten Produktivität verloren.
Dies ist asynchrones oder synchrones Verhalten
Genau das passiert in allen Beispielen unserer Frage. Das Laden eines Bildes, das Laden einer Datei von der Festplatte und das Anfordern einer Seite über AJAX sind alles langsame Vorgänge (im Kontext des modernen Rechnens).
Anstatt auf den Abschluss dieser langsamen Vorgänge zu warten , können Sie mit JavaScript eine Rückruffunktion registrieren, die ausgeführt wird, wenn der langsame Vorgang abgeschlossen ist. In der Zwischenzeit wird JavaScript jedoch weiterhin anderen Code ausführen. Die Tatsache, dass JavaScript anderen Code ausführt , während auf den langsamen Vorgang gewartet wird, macht das Verhalten asynchron . Hätte JavaScript gewartet, bis der Vorgang abgeschlossen war, bevor ein anderer Code ausgeführt wurde, wäre dies ein synchrones Verhalten gewesen.
Im obigen Code bitten wir JavaScript zu laden
lolcat.png
, was eine langsame Operation ist. Die Rückruffunktion wird ausgeführt, sobald diese langsame Operation ausgeführt wurde. In der Zwischenzeit verarbeitet JavaScript die nächsten Codezeilen weiter. dhalert(outerScopeVar)
.Aus diesem Grund wird die Warnung angezeigt
undefined
. da dasalert()
sofort verarbeitet wird, anstatt nachdem das Bild geladen wurde.Um unseren Code zu beheben, alles , was wir tun müssen , ist die bewegen
alert(outerScopeVar)
Code in der Callback - Funktion. Infolgedessen benötigen wir dieouterScopeVar
als globale Variable deklarierte Variable nicht mehr .Sie werden immer sehen, dass ein Rückruf als Funktion angegeben wird, da dies die einzige * Möglichkeit in JavaScript ist, Code zu definieren, ihn jedoch erst später auszuführen.
Daher ist in allen unseren Beispielen
function() { /* Do something */ }
der Rückruf; Um alle Beispiele zu korrigieren , müssen wir nur den Code, der die Antwort der Operation benötigt, dorthin verschieben!* Technisch können Sie auch verwenden
eval()
, ist aber für diesen Zweckeval()
böseWie lasse ich meinen Anrufer warten?
Möglicherweise haben Sie derzeit einen ähnlichen Code.
Jetzt wissen wir jedoch, dass
return outerScopeVar
dies sofort geschieht. bevor dieonload
Rückruffunktion die Variable aktualisiert hat. Dies führt dazu, dass SiegetWidthOfImage()
zurückkehrenundefined
undundefined
alarmiert werden.Um dies zu beheben, müssen wir dem Funktionsaufruf erlauben
getWidthOfImage()
, einen Rückruf zu registrieren, und dann die Warnung der Breite so verschieben, dass sie innerhalb dieses Rückrufs liegt.... beachten Sie nach wie vor, dass wir die globalen Variablen (in diesem Fall
width
) entfernen konnten .quelle
Hier finden Sie eine präzisere Antwort für Personen, die nach einer Kurzreferenz suchen, sowie einige Beispiele, die Versprechen und Async / Warten verwenden.
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:undefined
wird in diesem Fall protokolliert, dagetMessage
zurückgegeben wird, bevor dersetTimeout
Rückruf aufgerufen und aktualisiert wirdouterScopeVar
.Die beiden Hauptlösungsmöglichkeiten sind Rückrufe und Versprechen :
Rückrufe
Die Änderung hier ist, dass
getMessage
eincallback
Parameter akzeptiert wird, der aufgerufen wird, um die Ergebnisse an den aufrufenden Code zurückzusenden, sobald dieser verfügbar ist.Versprechen
Versprechen bieten eine Alternative, die flexibler ist als Rückrufe, da sie auf natürliche Weise kombiniert werden können, um mehrere asynchrone Vorgänge zu koordinieren. Eine Promises / A + -Standardimplementierung wird nativ in node.js (0.12+) und vielen aktuellen Browsern bereitgestellt, ist aber auch in Bibliotheken wie Bluebird und Q implementiert .
jQuery Deferreds
jQuery bietet Funktionen, die Versprechungen mit seinen Zurückgestellten ähneln.
async / warten
Wenn Ihre JavaScript-Umgebung Unterstützung für
async
undawait
(wie Node.js 7.6+) enthält, können Sie Versprechen synchron innerhalb vonasync
Funktionen verwenden:quelle
function getMessage(param1, param2, callback) {...}
.async/await
Probe, aber ich habe Probleme. Anstatt a zu instanziierennew Promise
,.Get()
rufe ich an und habe daher keinen Zugriff auf eineresolve()
Methode. Somit gibt meingetMessage()
das Versprechen zurück und nicht das Ergebnis. Könnten Sie Ihre Antwort ein wenig bearbeiten, um eine funktionierende Syntax dafür anzuzeigen?.Get()
Anruf meinen . Wahrscheinlich am besten eine neue Frage stellen.Um das Offensichtliche auszudrücken, repräsentiert die Tasse
outerScopeVar
.Asynchrone Funktionen sind wie ...
quelle
Die anderen Antworten sind ausgezeichnet und ich möchte nur eine direkte Antwort darauf geben. Nur auf asynchrone jQuery-Aufrufe beschränkt
Alle Ajax-Aufrufe (einschließlich
$.get
oder$.post
oder$.ajax
) sind asynchron.Betrachten Sie Ihr Beispiel
Die Codeausführung beginnt in Zeile 1, deklariert die Variable und löst einen asynchronen Aufruf in Zeile 2 (dh die Nachanforderung) aus und setzt ihre Ausführung in Zeile 3 fort, ohne darauf zu warten, dass die Nachanforderung ihre Ausführung abschließt.
Nehmen wir an, die Post-Anfrage dauert 10 Sekunden. Der Wert von
outerScopeVar
wird erst nach diesen 10 Sekunden festgelegt.Ausprobieren,
Wenn Sie dies jetzt ausführen, wird in Zeile 3 eine Warnung angezeigt. Warten Sie nun einige Zeit, bis Sie sicher sind, dass die Post-Anfrage einen Wert zurückgegeben hat. Wenn Sie dann im Warnfeld auf OK klicken, wird bei der nächsten Warnung der erwartete Wert gedruckt, da Sie darauf gewartet haben.
Im realen Szenario wird der Code,
Der gesamte Code, der von den asynchronen Aufrufen abhängt, wird innerhalb des asynchronen Blocks oder durch Warten auf die asynchronen Aufrufe verschoben.
quelle
or by waiting on the asynchronous calls
Wie macht man das?In all diesen Szenarien
outerScopeVar
wird ein Wert geändert oder asynchron zugewiesen oder tritt zu einem späteren Zeitpunkt auf (Warten oder Abhören eines Ereignisses), auf das die aktuelle Ausführung nicht wartet. In all diesen Fällen führt der aktuelle Ausführungsfluss zuouterScopeVar = undefined
Lassen Sie uns die einzelnen Beispiele diskutieren (ich habe den Teil markiert, der asynchron oder verzögert aufgerufen wird, damit einige Ereignisse auftreten):
1.
Hier registrieren wir einen Ereignislisten, der bei diesem bestimmten Ereignis ausgeführt wird. Hier wird das Bild geladen. Dann wird die aktuelle Ausführung mit den nächsten Zeilen fortgesetzt,
img.src = 'lolcat.png';
undalert(outerScopeVar);
währenddessen tritt das Ereignis möglicherweise nicht auf. Das heißt, die Funktionimg.onload
wartet asynchron , bis das referenzierte Bild geladen ist. Dies geschieht im folgenden Beispiel - das Ereignis kann unterschiedlich sein.2.
Hier spielt das Timeout-Ereignis die Rolle, die den Handler nach der angegebenen Zeit aufruft. Hier ist es
0
, aber es registriert immer noch ein asynchrones Ereignis, das an die letzte Position derEvent Queue
zur Ausführung angefügt wird , wodurch die garantierte Verzögerung erreicht wird.3.
Diesmal Ajax-Rückruf.
4.
Der Knoten kann als König der asynchronen Codierung betrachtet werden. Hier wird die markierte Funktion als Rückruf-Handler registriert, der nach dem Lesen der angegebenen Datei ausgeführt wird.
5.
Offensichtliches Versprechen (etwas wird in Zukunft getan) ist asynchron. Siehe Was sind die Unterschiede zwischen Zurückgestellt, Versprechen und Zukunft in JavaScript?
https://www.quora.com/Whats-the-difference-between-a-promise-and-a-callback-in-Javascript
quelle