Odpowiedź Fabrício jest natychmiastowa; ale chciałem uzupełnić jego odpowiedź o coś mniej technicznego, co koncentruje się na analogii, która pomoże wyjaśnić pojęcie asynchroniczności .
Analogia ...
Wczoraj praca, którą wykonywałem, wymagała od kolegi pewnych informacji. Zadzwoniłem do niego; oto jak przebiegła rozmowa:
Me : Hi Bob, muszę wiedzieć jak foo 'D bar ' d ostatniego tygodnia. Jim chce raport na ten temat, a ty jesteś jedynym, który zna szczegóły na ten temat.
Bob : Jasne, ale zajmie mi to około 30 minut?
Ja : To świetnie, Bob. Oddzwoń, gdy zdobędziesz informacje!
W tym momencie odłożyłem słuchawkę. Ponieważ potrzebowałem informacji od Boba, aby ukończyć raport, opuściłem raport i zamiast tego poszedłem na kawę, a potem dostałem e-maila. 40 minut później (Bob jest wolny), Bob oddzwonił i dał mi informacje, których potrzebowałem. W tym momencie wznowiłem pracę z moim raportem, ponieważ miałem wszystkie informacje, których potrzebowałem.
Wyobraź sobie, że zamiast tego rozmowa przebiegła w ten sposób;
Me : Hi Bob, muszę wiedzieć jak foo 'D bar ' d ostatniego tygodnia. Jim want ma raport na ten temat, a ty jesteś jedynym, który zna szczegóły na ten temat.
Bob : Jasne, ale zajmie mi to około 30 minut?
Ja : To świetnie, Bob. Poczekam.
I siedziałem tam i czekałem. I czekałem. I czekałem. Przez 40 minut. Nie robiąc nic, tylko czekając. W końcu Bob przekazał mi informacje, rozłączyliśmy się i ukończyłem raport. Ale straciłem 40 minut wydajności.
Jest to zachowanie asynchroniczne vs. synchroniczne
Tak właśnie dzieje się we wszystkich przykładach w naszym pytaniu. Ładowanie obrazu, ładowanie pliku z dysku i żądanie strony za pośrednictwem AJAX to powolne operacje (w kontekście nowoczesnego przetwarzania).
Zamiast czekać na zakończenie tych powolnych operacji, JavaScript pozwala zarejestrować funkcję zwrotną, która zostanie wykonana po zakończeniu powolnej operacji. Jednak w międzyczasie JavaScript będzie nadal wykonywać inny kod. Fakt, że JavaScript wykonuje inny kod w oczekiwaniu na zakończenie powolnej operacji, powoduje, że zachowanie jest asynchroniczne . Gdyby JavaScript czekał na zakończenie operacji przed wykonaniem jakiegokolwiek innego kodu, byłoby to zachowanie synchroniczne .
var outerScopeVar;
var img = document.createElement('img');
// Here we register the callback function.
img.onload = function() {
// Code within this function will be executed once the image has loaded.
outerScopeVar = this.width;
};
// But, while the image is loading, JavaScript continues executing, and
// processes the following lines of JavaScript.
img.src = 'lolcat.png';
alert(outerScopeVar);
W powyższym kodzie prosimy JavaScript do załadowania lolcat.png
, co jest powolną operacją. Funkcja wywołania zwrotnego zostanie wykonana po zakończeniu tej powolnej operacji, ale w międzyczasie JavaScript będzie przetwarzać kolejne wiersze kodu; tj alert(outerScopeVar)
.
Dlatego widzimy ostrzeżenie undefined
; ponieważ alert()
jest on przetwarzany natychmiast, a nie po załadowaniu obrazu.
Aby naprawić nasz kod, wystarczy przenieść alert(outerScopeVar)
kod do funkcji wywołania zwrotnego. W rezultacie nie potrzebujemy już outerScopeVar
zmiennej zadeklarowanej jako zmienna globalna.
var img = document.createElement('img');
img.onload = function() {
var localScopeVar = this.width;
alert(localScopeVar);
};
img.src = 'lolcat.png';
Będziesz zawsze zobaczyć zwrotna jest określona jako funkcja, ponieważ jest to jedyny sposób w JavaScript * zdefiniować jakiś kod, ale nie wykonać ją później.
Dlatego we wszystkich naszych przykładach function() { /* Do something */ }
jest to callback; aby naprawić wszystkie przykłady, wystarczy przenieść tam kod, który wymaga odpowiedzi operacji!
* Technicznie możesz też użyć eval()
, ale eval()
jest zły do tego celu
Jak sprawić, by mój rozmówca czekał?
Być może masz obecnie kod podobny do tego;
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);
Wiemy jednak, że return outerScopeVar
dzieje się to natychmiast; zanim onload
funkcja wywołania zwrotnego zaktualizuje zmienną. Prowadzi to do getWidthOfImage()
powrotu undefined
i undefined
otrzymania ostrzeżenia.
Aby to naprawić, musimy pozwolić funkcji wywołującej getWidthOfImage()
na zarejestrowanie wywołania zwrotnego, a następnie przesunąć ostrzeżenie o szerokości, aby znajdowało się w obrębie tego wywołania zwrotnego;
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);
});
... jak poprzednio, zauważ, że byliśmy w stanie usunąć zmienne globalne (w tym przypadku width
).