Dlaczego moja zmienna pozostaje niezmieniona po zmodyfikowaniu jej wewnątrz funkcji? - Asynchroniczny kod odniesienia
Biorąc pod uwagę następujące przykłady, dlaczego outerScopeVar
jest niezdefiniowany we wszystkich przypadkach?
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 undefined
we wszystkich tych przykładach? Nie chcę obejść, chcę wiedzieć, dlaczego to się dzieje.
Uwaga: jest to kanoniczne pytanie dla Asynchroniczności JavaScript . Zachęcamy do ulepszenia tego pytania i dodania bardziej uproszczonego przykłady, z którymi Wspólnota może się utożsamiać.
6 answers
Odpowiedź jednym słowem: asynchroniczność .
Forewords
Ten temat był powtarzany co najmniej kilka tysięcy razy, tutaj, w Stack Overflow. W związku z tym, po pierwsze chciałbym zwrócić uwagę na kilka niezwykle przydatnych zasobów: {]}
@odpowiedź Matta Escha na "Pobierz dane z fs.readFile " bardzo dobrze wyjaśnia asynchroniczność w prosty sposób.
Odpowiedź na pytanie pod ręką
Najpierw prześledzimy wspólne zachowanie. We wszystkich przykładachouterScopeVar
jest modyfikowana wewnątrz funkcji. Ta funkcja nie jest wykonywana natychmiast, jest przypisywana lub przekazane jako argument. To właśnie nazywamy callback.
Teraz pytanie brzmi, kiedy to wywołanie callback?
To zależy od sprawy. Spróbujmy jeszcze raz prześledzić jakieś typowe zachowanie: {]}- {[3] } może być wywołane w przyszłości , kiedy (i jeśli) obraz zostanie pomyślnie załadowany.
-
setTimeout
może być wywołanakiedyś w przyszłości , po tym, jak opóźnienie upłynęło, a limit czasu nie został anulowany przezclearTimeout
. Uwaga: nawet jeśli używasz0
jako opóźnienia, wszystkie przeglądarki mają minimalny limit opóźnienia (określony jako 4MS w specyfikacji HTML5).
Wywołanie zwrotne jQuery - węzeł.js
fs.readFile
może być wywołany kiedyś w przyszłości , gdy plik został odczytany pomyślnie lub wystąpił błąd.
$.post
może być wywołane kiedyś w przyszłości, kiedy (i jeśli) żądanie Ajax zostało pomyślnie zakończone.
We wszystkich przypadkach, mamy callback, który może uruchomić kiedyś w przyszłość . To" kiedyś w przyszłości " jest tym, co nazywamy asynchronicznym przepływem .
Wykonanie asynchroniczne jest wypychane z przepływu synchronicznego. Oznacza to, że kod asynchroniczny nigdy nie będzie wykonywany podczas wykonywania stosu kodu synchronicznego. Takie jest znaczenie języka JavaScript jako jednowątkowego.
Dokładniej, gdy silnik JS jest bezczynny-nie wykonuje stosu (a) kodu synchronicznego - będzie sprawdzał zdarzenia, które mogą mieć wyzwalane asynchroniczne wywołania zwrotne (np. wygasły timeout, odebrana odpowiedź sieciowa) i wykonują je jeden po drugim. Jest to traktowane jako pętla zdarzeń .
Kod asynchroniczny podświetlony na czerwono może zostać wykonany dopiero po wykonaniu wszystkich pozostałych kodów synchronicznych w odpowiednich blokach kodu:]}Krótko mówiąc, funkcje wywołania zwrotnego są tworzone synchronicznie, ale wykonywane asynchronicznie. Po prostu nie możesz polegać na wykonywanie funkcji asynchronicznej, dopóki nie wiesz, że została wykonana, i jak to zrobić?
To naprawdę proste. Logika zależna od wykonania funkcji asynchronicznej powinna być uruchamiana / wywoływana z wewnątrz tej funkcji asynchronicznej. Na przykład, przeniesieniealert
S I console.log
S zbyt wewnątrz funkcji wywołania zwrotnego spowoduje oczekiwany wynik, ponieważ wynik jest dostępny w tym momencie.
Implementowanie własnej logiki wywołania zwrotnego
Często trzeba zrobić więcej rzeczy z wynikiem z funkcji asynchronicznej lub zrobić różne rzeczy z wynikiem w zależności od tego, gdzie funkcja asynchroniczna została wywołana. Zajmijmy się nieco bardziej złożonym przykładem:
var outerScopeVar;
helloCatAsync();
alert(outerScopeVar);
function helloCatAsync() {
setTimeout(function() {
outerScopeVar = 'Nya';
}, Math.random() * 2000);
}
Uwaga: używam setTimeout
z losowym opóźnieniem jako ogólnej funkcji asynchronicznej, ten sam przykład dotyczy Ajax, readFile
, onload
i każdy inny przepływ asynchroniczny.
Ten przykład wyraźnie cierpi na ten sam problem, co inne przykłady, nie czeka dopóki funkcja asynchroniczna nie zostanie wykonana.
Zajmijmy się tym wdrażając własny system oddzwaniania. Po pierwsze, pozbędziemy się tego brzydkiego outerScopeVar
, który jest całkowicie bezużyteczny w tym przypadku. Następnie dodajemy parametr, który akceptuje argument funkcji, nasz callback. Po zakończeniu operacji asynchronicznej wywołujemy to wywołanie zwrotne przekazujące wynik. Realizacja (proszę przeczytać komentarze w kolejności):
// 1. Call helloCatAsync passing a callback function,
// which will be called receiving the result from the async operation
helloCatAsync(function(result) {
// 5. Received the result from the async function,
// now do whatever you want with it:
alert(result);
});
// 2. The "callback" parameter is a reference to the function which
// was passed as argument from the helloCatAsync call
function helloCatAsync(callback) {
// 3. Start async operation:
setTimeout(function() {
// 4. Finished async operation,
// call the callback passing the result as argument
callback('Nya');
}, Math.random() * 2000);
}
Najczęściej w rzeczywistych przypadkach użycia, DOM API i większość bibliotek już podaj funkcjonalność wywołania zwrotnego (implementacja helloCatAsync
w tym demonstracyjnym przykładzie). Wystarczy tylko przekazać funkcję zwrotną i zrozumieć, że będzie ona wykonywać z przepływu synchronicznego i zrestrukturyzować kod, aby pomieścić do tego.
Można również zauważyć, że ze względu na asynchroniczny charakter, niemożliwe jest return
wartość z asynchronicznego przepływu z powrotem do synchronicznego przepływu, w którym zostało zdefiniowane wywołanie zwrotne, ponieważ asynchroniczne wywołania zwrotne są wykonywane długo po zakończeniu wykonywania kodu synchronicznego.
Zamiast return
ING wartość z asynchronicznego wywołania zwrotnego, będziesz musiał użyć wzorca wywołania zwrotnego, or. .. Obietnice.
Obietnice
Chociaż istnieją sposoby, aby utrzymać callback hell na dystans z vanilla JS, promises cieszą się coraz większą popularnością i są obecnie standaryzowane w ES6 (zobacz Promise - MDN ).
Obietnice (a.k.a. Futures) zapewniają bardziej liniowy, a co za tym idzie przyjemna lektura kodu asynchronicznego, ale wyjaśnienie całej ich funkcjonalności nie wchodzi w zakres tego pytania. Zamiast tego zostawię te doskonałe zasoby dla zainteresowanych: {]}
Więcej materiałów na temat Asynchroniczności JavaScript
- Sztuka wywoływania węzłów wyjaśnia asynchroniczny kod i wywołania bardzo dobrze z przykładami vanilla JS i Node.kod js również.
Uwaga: zaznaczyłem tę odpowiedź jako Community Wiki, stąd każdy, kto ma przynajmniej 100 reputacji, może ją edytować i ulepszać! Prosimy o poprawienie tej odpowiedzi lub przesłanie zupełnie nowej odpowiedzi, jeśli chcesz, jak również.
Chcę przekształcić to pytanie w kanoniczny temat, aby odpowiedzieć na problemy asynchroniczności, które nie są związane z Ajaxem (jest Jak zwrócić odpowiedź z połączenia AJAX? do tego), dlatego ten temat potrzebuje twojej pomocy, aby być tak dobrym i pomocnym, jak to możliwe!
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-12 13:14:27
odpowiedź Fabrício jest trafna, ale chciałem uzupełnić jego odpowiedź o coś mniej technicznego, co skupia się na analogii, aby pomóc wyjaśnić pojęcie asynchroniczności [22].
Analogia...
Wczoraj moja praca wymagała pewnych informacji od kolegi. Zadzwoniłem do niego; Oto jak przebiegła rozmowa:
Ja : Cześć Bob, muszę wiedzieć, jak my foo 'D bar 'D w zeszłym tygodniu. Jim chce zgłoś to, a tylko Ty znasz szczegóły.
Bob: jasne, ale zajmie mi to około 30 minut?
W tym momencie rozłączyłem się. Ponieważ potrzebowałem informacji od Boba, aby zakończyć mój raport, zostawiłem raport i poszedłem na kawę, a następnie nadrobiłem trochę e-maila. 40 minut później (Bob jest powolny), Bob oddzwonił i dał mi informacje potrzebne. W tym momencie wznowiłem pracę z moim sprawozdaniem, ponieważ miałem wszystkie potrzebne informacje.Ja: to świetnie Bob. Oddzwoń, jak będziesz miał informacje!
Wyobraź sobie, że rozmowa potoczyłaby się w ten sposób;
Ja : Cześć Bob, muszę wiedzieć, jak my foo 'D bar 'D w zeszłym tygodniu. Jim chce raportu i tylko Ty znasz szczegóły.
Bob: jasne, ale zajmie mi to jakieś 30 minut?
Siedziałam tam i czekałam. I czekał. I czekał. Przez 40 minut. Nie robiąc nic poza czekaniem. W końcu Bob dał mi informacje, rozłączyliśmy się i skończyłem raport. Ale straciłem 40 minut produktywności.Ja: to świetnie Bob. Poczekam.
Jest to zachowanie asynchroniczne i synchroniczne.]}
To jest dokładnie to, co dzieje się we wszystkich przykładach w naszym pytaniu. Wczytywanie obrazu, wczytywanie pliku z dysku i żądanie strony za pomocą AJAX to powolne operacje (w kontekście nowoczesnych komputerów).
Zamiast oczekiwania na zakończenie tych powolnych operacji, JavaScript pozwala zarejestrować funkcję zwrotną, która zostanie wykonana po zakończeniu powolnej operacji. W międzyczasie jednak JavaScript będzie nadal wykonywał inny kod. Fakt, że JavaScript wykonuje inny kod czekając na powolne operacja zakończona powoduje, że zachowaniejest asynchroniczne. Gdyby JavaScript czekał na zakończenie operacji przed wykonaniem innego kodu, byłoby to zachowanie synchronous .
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 o załadowanie lolcat.png
, co jest operacją sloooow. Funkcja callback zostanie wykonana po wykonaniu tej powolnej operacji, ale w międzyczasie JavaScript będzie nadal przetwarzał kolejne wiersze kodu; tj. alert(outerScopeVar)
.
Dlatego widzimy alert pokazujący undefined
; ponieważ {[7] } jest przetwarzany natychmiast, a nie po załadowaniu obrazu.
Aby naprawić nasz kod, wystarczy przenieść alert(outerScopeVar)
kod do funkcji callback. W konsekwencji nie potrzebujemy już zmiennej outerScopeVar
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 zobacz callback jest określony jako funkcja, ponieważ jest to jedyny * sposób w JavaScript, aby zdefiniować jakiś kod, ale nie wykonać go dopiero później.
Dlatego we wszystkich naszych przykładach, function() { /* Do something */ }
jest wywołaniem zwrotnym; aby naprawić wszystkie przykłady, wszystko, co musimy zrobić, to przenieść kod, który wymaga reakcji operacji tam!
* technicznie można również użyć eval()
, ale eval()
jest złem w tym celu
Jak sprawić, by mój rozmówca czekał?
Możesz mieć obecnie kod podobny do to;
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);
Jednak teraz wiemy, że return outerScopeVar
dzieje się natychmiast; przed onload
funkcja callback zaktualizowała zmienną. To prowadzi do getWidthOfImage()
powrotu undefined
i undefined
bycia zaalarmowanym.
getWidthOfImage()
, aby zarejestrować wywołanie zwrotne, a następnie przesunąć alert ' ING o szerokości, która będzie w tym wywołaniu zwrotnym;
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);
});
... tak jak wcześniej, zauważ, że udało nam się usunąć zmienne globalne (w tym przypadku width
).
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-12 13:16:20
Oto bardziej zwięzła odpowiedź dla osób, które szukają szybkiego odniesienia, a także kilka przykładów z wykorzystaniem obietnic i asynchronicznych / oczekujących.
Zacznij od naiwnego podejścia (które nie działa) dla funkcji, która wywołuje metodę asynchroniczną (w tym przypadku setTimeout
) i zwraca wiadomość:
function getMessage() {
var outerScopeVar;
setTimeout(function() {
outerScopeVar = 'Hello asynchronous world!';
}, 0);
return outerScopeVar;
}
console.log(getMessage());
undefined
jest zalogowany w tym przypadku, ponieważ getMessage
zwraca przed wywołaniem wywołania zwrotnego setTimeout
i aktualizacją outerScopeVar
.
Dwa główne sposoby jej rozwiązania to użycie callbacks and promises :
Callbacks
Zmiana polega na tym, że getMessage
przyjmuje parametr callback
, który zostanie wywołany w celu dostarczenia wyników z powrotem do kodu wywołującego, gdy będzie dostępny.
function getMessage(callback) {
setTimeout(function() {
callback('Hello asynchronous world!');
}, 0);
}
getMessage(function(message) {
console.log(message);
});
Obietnice zapewniają alternatywę, która jest bardziej elastyczna niż wywołania zwrotne, ponieważ mogą być naturalnie łączone w celu koordynowania wielu operacji asynchronicznych. A obietnice/a+ standardowa implementacja jest natywnie dostarczany w node.js (0.12+) i wielu aktualnych przeglądarkach, ale jest również zaimplementowany w bibliotekach takich jak Bluebird I Q .
function getMessage() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('Hello asynchronous world!');
}, 0);
});
}
getMessage().then(function(message) {
console.log(message);
});
JQuery zapewnia funkcjonalność, która jest podobna do obietnic z jego Deferreds.
function getMessage() {
var deferred = $.Deferred();
setTimeout(function() {
deferred.resolve('Hello asynchronous world!');
}, 0);
return deferred.promise();
}
getMessage().done(function(message) {
console.log(message);
});
Async / wait
Jeśli Twoje środowisko JavaScript zawiera wsparcie dla async
oraz await
(Jak Node.js 7.6+), wtedy można użyć obietnic synchronously within async
functions:
function getMessage () {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('Hello asynchronous world!');
}, 0);
});
}
async function main() {
let message = await getMessage();
console.log(message);
}
main();
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-22 05:45:24
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-08 17:01:14
Inne odpowiedzi są doskonałe i chcę tylko udzielić prostej odpowiedzi na to pytanie. Po prostu ograniczam się 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
var outerScopeVar; //line 1
$.post('loldog', function(response) { //line 2
outerScopeVar = response;
});
alert(outerScopeVar); //line 3
Wykonywanie kodu rozpoczyna się od linii 1, deklaruje zmienną oraz wyzwalacze i asynchroniczne wywołanie w linii 2 (tzn. żądanie post) i kontynuuje jego wykonywanie z linii 3, nie czekając na żądanie post do dokończ jego wykonanie.
Powiedzmy, że zakończenie żądania post zajmuje 10 sekund, wartość outerScopeVar
zostanie ustawiona dopiero po tych 10 sekundach.
Aby wypróbować,
var outerScopeVar; //line 1
$.post('loldog', function(response) { //line 2, takes 10 seconds to complete
outerScopeVar = response;
});
alert("Lets wait for some time here! Waiting is fun"); //line 3
alert(outerScopeVar); //line 4
Kiedy to wykonasz, otrzymasz alarm na linii 3. Teraz poczekaj jakiś czas, aż będziesz pewien, że żądanie post zwróciło jakąś wartość. Następnie po kliknięciu OK, w polu alert, następny alert wydrukuje wartość oczekiwaną, ponieważ czekałeś na nią.
W realnym scenariuszu, kod staje się,
var outerScopeVar;
$.post('loldog', function(response) {
outerScopeVar = response;
alert(outerScopeVar);
});
Cały kod zależny od wywołań asynchronicznych jest przenoszony wewnątrz bloku asynchronicznego lub przez oczekiwanie na wywołania asynchroniczne.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-26 03:59:09
We wszystkich tych scenariuszach outerScopeVar
jest modyfikowana lub przypisywana wartość asynchronicznie lub dzieje się w późniejszym czasie (oczekiwanie lub nasłuchiwanie jakiegoś zdarzenia),na które bieżące wykonanie nie będzie czekać .]}
Omówmy każdy przykład (zaznaczyłem część, która jest nazywana asynchronicznie lub opóźniona dla niektórych zdarzeń):
1.
Tutaj rejestrujemy eventlistner, który zostanie wykonany na danym wydarzeniu.Tutaj ładowanie obrazu.Następnie bieżące wykonanie ciągłe z kolejnymi liniami img.src = 'lolcat.png';
i alert(outerScopeVar);
tymczasem zdarzenie może nie wystąpić. tj. funtion img.onload
Poczekaj, aż dany obraz załaduje się asynchronicznie. Stanie się tak cały następujący przykład - zdarzenie może się różnić.
2.
Tutaj rolę odgrywa Zdarzenie timeout, które wywoła procedurę obsługi po określonym czasie. Proszę. jest 0
, ale nadal rejestruje zdarzenie asynchroniczne, które zostanie dodane do ostatniej pozycji Event Queue
do wykonania, co powoduje gwarantowane opóźnienie.
3.
4.
Węzeł może być uważany za króla kodowania asynchronicznego.Tutaj zaznaczona funkcja jest rejestrowana jako Obsługa wywołania zwrotnego, która zostanie wykonana po odczytaniu podanego plik.
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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:36