Biorąc pod uwagę następujące przykłady, dlaczego outerScopeVar
we wszystkich przypadkach jest niezdefiniowany?
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);
Dlaczego wyświetla się undefined
we wszystkich tych przykładach? Nie chcę obejść tego problemu, chcę wiedzieć, dlaczego tak się dzieje.
Uwaga: jest to kanoniczne pytanie dotyczące asynchroniczności JavaScript . Popraw to pytanie i dodaj bardziej uproszczone przykłady, z którymi społeczność może się identyfikować.
javascript
asynchronous
Fabrício Matté
źródło
źródło
Odpowiedzi:
Odpowiedź na jedno słowo: asynchroniczność .
Przedmowy
Ten temat został powtórzony co najmniej kilka tysięcy razy tutaj, w przepełnieniu stosu. Dlatego najpierw chciałbym wskazać kilka niezwykle przydatnych zasobów:
Odpowiedź Felixa Klinga na „Jak zwrócić odpowiedź z wywołania asynchronicznego” . Zobacz jego doskonałą odpowiedź wyjaśniającą przepływy synchroniczne i asynchroniczne, a także sekcję „Kod restrukturyzacji”.
@Benjamin Gruenbaum również włożył wiele wysiłku w wyjaśnienie asynchroniczności w tym samym wątku.
Odpowiedź @Matta Escha na „Pobierz dane z pliku fs.readFile” również bardzo dobrze wyjaśnia asynchroniczność w prosty sposób.
Odpowiedź na pytanie
Najpierw prześledźmy wspólne zachowanie. We wszystkich przykładach
outerScopeVar
jest modyfikowany wewnątrz funkcji . Ta funkcja najwyraźniej nie jest natychmiast wykonywana, jest przypisywana lub przekazywana jako argument. To nazywamy oddzwanianiem .Teraz pytanie brzmi, kiedy wywoływane jest to oddzwanianie?
To zależy od przypadku. Spróbujmy ponownie prześledzić niektóre typowe zachowania:
img.onload
może zostać wywołany w przyszłości , gdy (i jeśli) obraz zostanie pomyślnie załadowany.setTimeout
może zostać wywołany w przyszłości , po upływie opóźnienia i przekroczeniu limitu czasuclearTimeout
. Uwaga: nawet jeśli używasz0
jako opóźnienia, wszystkie przeglądarki mają minimalny limit opóźnienia (określony jako 4ms w specyfikacji HTML5).$.post
zwrotne jQuery może zostać wywołane w przyszłości , gdy (i jeśli) żądanie Ajax zostanie pomyślnie zakończone.fs.readFile
może zostać wywołany w przyszłości , gdy plik zostanie odczytany pomyślnie lub wystąpi błąd.We wszystkich przypadkach mamy wywołanie zwrotne, które może zostać uruchomione w przyszłości . To „kiedyś w przyszłości” nazywamy przepływem asynchronicznym .
Wykonanie asynchroniczne jest wypychane z przepływu synchronicznego. Oznacza to, że kod asynchroniczny nigdy się nie uruchomi podczas wykonywania stosu kodu synchronicznego. Takie jest znaczenie JavaScript jako jednowątkowego.
Mówiąc dokładniej, gdy silnik JS jest w stanie bezczynności - nie wykonuje stosu (a) kodu synchronicznego - będzie sondował w poszukiwaniu zdarzeń, które mogły wywołać asynchroniczne wywołania zwrotne (np. Upłynął limit czasu, odebrano odpowiedź sieci) i wykonał je jeden po drugim. Jest to traktowane jako pętla zdarzeń .
Oznacza to, że kod asynchroniczny wyróżniony w ręcznie rysowanych czerwonych kształtach może zostać wykonany tylko po wykonaniu całego pozostałego kodu synchronicznego w odpowiednich blokach kodu:
Krótko mówiąc, funkcje zwrotne są tworzone synchronicznie, ale wykonywane asynchronicznie. Po prostu nie możesz polegać na wykonaniu funkcji asynchronicznej, dopóki nie dowiesz się, że została ona wykonana, i jak to zrobić?
To naprawdę proste. Logikę zależną od wykonania funkcji asynchronicznej należy uruchomić / wywołać z wnętrza tej funkcji asynchronicznej. Na przykład przesunięcie
alert
siconsole.log
si wewnątrz funkcji wywołania zwrotnego wygeneruje oczekiwany wynik, ponieważ wynik jest dostępny w tym momencie.Implementowanie własnej logiki zwrotnej
Często trzeba robić więcej rzeczy z wynikiem funkcji asynchronicznej lub robić różne rzeczy z wynikiem w zależności od tego, gdzie wywołano funkcję asynchroniczną. Zajmijmy się nieco bardziej złożonym przykładem:
Uwaga: Używam
setTimeout
z losowym opóźnieniem jako rodzajowy funkcji asynchronicznego, ten sam przykład odnosi się do AjaksureadFile
,onload
a także wszelkie inne przepływu asynchroniczny.W tym przykładzie wyraźnie występuje ten sam problem, co w innych przykładach, nie czeka on na wykonanie funkcji asynchronicznej.
Zajmijmy się tym, wdrażając własny system oddzwaniania. Po pierwsze, pozbywamy się tego brzydkiego,
outerScopeVar
co w tym przypadku jest całkowicie bezużyteczne. Następnie dodajemy parametr, który akceptuje argument funkcji, nasze wywołanie zwrotne. Po zakończeniu operacji asynchronicznej wywołujemy to wywołanie zwrotne, przekazując wynik. Realizacja (proszę przeczytać komentarze w kolejności):Fragment kodu powyższego przykładu:
Najczęściej w rzeczywistych przypadkach użycia DOM API i większość bibliotek już udostępnia funkcję wywołania zwrotnego (
helloCatAsync
implementacja w tym przykładzie demonstracyjnym). Musisz tylko przekazać funkcję wywołania zwrotnego i zrozumieć, że zostanie ona wykonana poza przepływem synchronicznym, i zrestrukturyzuj kod, aby to uwzględnić.Zauważysz również, że ze względu na charakter asynchroniczny niemożliwe jest
return
przejście z przepływu asynchronicznego z powrotem do przepływu synchronicznego, w którym zdefiniowano wywołanie zwrotne, ponieważ wywołania asynchroniczne są wykonywane długo po zakończeniu wykonywania kodu synchronicznego.Zamiast
return
wprowadzać wartość z asynchronicznego wywołania zwrotnego, będziesz musiał użyć wzorca wywołania zwrotnego lub ... Obiecuje.Obietnice
Chociaż istnieją sposoby na powstrzymanie piekła wywołania zwrotnego z waniliowym JS, obietnice rosną w popularności i są obecnie standaryzowane w ES6 (patrz Obietnica - MDN ).
Obietnice (inaczej Futures) zapewniają bardziej liniowy, a zatem przyjemny, odczyt kodu asynchronicznego, ale wyjaśnienie ich całej funkcjonalności nie wchodzi w zakres tego pytania. Zamiast tego pozostawię te doskonałe zasoby zainteresowanym:
Więcej materiałów do czytania na temat asynchroniczności JavaScript
źródło
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:
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;
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 mój 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 pomocą AJAX to powolne operacje (w kontekście współczesnego 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 .
W powyższym kodzie prosimy JavaScript do załadowania
lolcat.png
, co jest powolną operacją. Funkcja wywołania zwrotnego zostanie wykonana po wykonaniu tej powolnej operacji, ale w międzyczasie JavaScript będzie przetwarzać kolejne wiersze kodu; tjalert(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.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()
, aleeval()
jest zły do tego celuJak sprawić, by mój rozmówca czekał?
Być może masz obecnie kod podobny do tego;
Wiemy jednak, że
return outerScopeVar
dzieje się to natychmiast; zanimonload
funkcja wywołania zwrotnego zaktualizuje zmienną. Prowadzi to dogetWidthOfImage()
powrotuundefined
iundefined
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;... tak jak poprzednio, pamiętaj, że byliśmy w stanie usunąć zmienne globalne (w tym przypadku
width
).źródło
Oto bardziej zwięzła odpowiedź dla osób, które szukają szybkiego odniesienia, a także kilka przykładów wykorzystujących obietnice i asynchronizuj / czekaj.
Rozpocznij od naiwnego podejścia (które nie działa) dla funkcji, która wywołuje metodę asynchroniczną (w tym przypadku
setTimeout
) i zwraca komunikat:undefined
zostaje zalogowany w tym przypadku, ponieważgetMessage
wraca przedsetTimeout
wywołaniem oddzwaniania i aktualizuje sięouterScopeVar
.Dwoma głównymi sposobami rozwiązania tego problemu są połączenia zwrotne i obietnice :
Callbacki
Zmiana polega na
getMessage
zaakceptowaniucallback
parametru, który zostanie wywołany w celu dostarczenia wyników z powrotem do kodu wywołującego, gdy będzie dostępny.Obietnice
Obietnice stanowią alternatywę, która jest bardziej elastyczna niż wywołania zwrotne, ponieważ można je naturalnie łączyć w celu koordynowania wielu operacji asynchronicznych. Promises / A + standardowy realizacja jest warunkiem natywnie w node.js (0.12+) oraz wielu obecnych przeglądarek, ale jest również realizowany w bibliotekach, jak Bluebird i Q .
jQuery Deferreds
jQuery zapewnia funkcjonalność podobną do obietnic z Odroczonymi.
async / czekaj
Jeśli twoje środowisko JavaScript obsługuje
async
iawait
(jak Node.js 7.6+), możesz używać obietnic synchronicznie w ramachasync
funkcji:źródło
function getMessage(param1, param2, callback) {...}
.async/await
próbki, ale mam problemy. Zamiast tworzenia anew Promise
, wykonuję.Get()
połączenie i dlatego nie mam dostępu do żadnejresolve()
metody. W ten sposóbgetMessage()
zwracam obietnicę, a nie wynik. Czy mógłbyś trochę edytować swoją odpowiedź, aby pokazać działającą składnię?.Get()
połączenia. Prawdopodobnie najlepiej opublikować nowe pytanie.Mówiąc oczywiste, puchar reprezentuje
outerScopeVar
.Funkcje asynchroniczne są jak ...
źródło
Pozostałe odpowiedzi są doskonałe, a ja po prostu chcę udzielić bezpośredniej odpowiedzi na to pytanie. Tylko ograniczenie do asynchronicznych wywołań jQuery
Wszystkie wywołania ajax (w tym
$.get
lub$.post
lub$.ajax
) są asynchroniczne.Biorąc pod uwagę twój przykład
Wykonanie kodu rozpoczyna się od wiersza 1, deklaruje zmienną oraz wyzwala i asynchroniczne wywołanie w wierszu 2 (tj. Żądanie postu) i kontynuuje wykonywanie od linii 3, nie czekając na zakończenie żądania postu.
Powiedzmy, że wykonanie żądania pocztowego zajmuje 10 sekund, wartość
outerScopeVar
zostanie ustawiona dopiero po tych 10 sekundach.Spróbować,
Teraz, kiedy to wykonasz, dostaniesz alert w linii 3. Teraz poczekaj trochę czasu, aż będziesz pewien, że żądanie post zwróciło pewną wartość. Następnie po kliknięciu OK w polu alertu następny alert wydrukuje oczekiwaną wartość, ponieważ na nią czekałeś.
W scenariuszu z życia kod staje się
Cały kod, który zależy od wywołań asynchronicznych, jest przenoszony do bloku asynchronicznego lub przez oczekiwanie na wywołania asynchroniczne.
źródło
or by waiting on the asynchronous calls
Jak to zrobić?We wszystkich tych scenariuszach
outerScopeVar
modyfikuje się lub przypisuje wartość asynchronicznie lub dzieje się w późniejszym czasie (oczekiwanie lub oczekiwanie na wystąpienie jakiegoś zdarzenia), na które bieżące wykonanie nie będzie czekać . Więc we wszystkich tych przypadkach bieżący przepływ wykonania powodujeouterScopeVar = undefined
Omówmy każdy przykład (zaznaczyłem część, która jest wywoływana asynchronicznie lub opóźniona w przypadku wystąpienia niektórych zdarzeń):
1.
Tutaj rejestrujemy listę zdarzeń, która zostanie wykonana po tym konkretnym zdarzeniu. Tutaj ładowanie obrazu. Następnie bieżące wykonanie jest kontynuowane z kolejnymi wierszami,
img.src = 'lolcat.png';
aalert(outerScopeVar);
tymczasem zdarzenie może się nie pojawić. tzn. funkcjaimg.onload
czeka asynchronicznie na załadowanie wskazanego obrazu. Stanie się tak na każdym z poniższych przykładów - wydarzenie może się różnić.2)
Tutaj zdarzenie timeout odgrywa rolę, która wywoła procedurę obsługi po określonym czasie. Tutaj jest
0
, ale nadal rejestruje zdarzenie asynchroniczne, zostanie dodane do ostatniej pozycjiEvent Queue
wykonania, co gwarantuje gwarantowane opóźnienie.3)
Tym razem oddzwanianie ajax.
4
Węzeł można uznać za króla kodowania asynchronicznego. Tutaj zaznaczona funkcja jest rejestrowana jako moduł obsługi wywołania zwrotnego, który zostanie wykonany po odczytaniu określonego pliku.
5
Oczywista obietnica (coś zostanie zrobione w przyszłości) jest asynchroniczna. zobacz Jakie są różnice między odroczeniem, obietnicą i przyszłością w JavaScript?
https://www.quora.com/Whats-the-difference-between-a-promise-and-a-callback-in-Javascript
źródło