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.