→ Aby uzyskać bardziej ogólne wyjaśnienie zachowania asynchronicznego z różnymi przykładami, zobacz Dlaczego moja zmienna pozostaje niezmieniona po zmodyfikowaniu jej w funkcji? - Odwołanie do kodu asynchronicznego
→ Jeśli już rozumiesz problem, przejdź do możliwych rozwiązań poniżej.
Problem
W Ajax oznacza asynchroniczny . Oznacza to, że wysłanie żądania (a raczej otrzymanie odpowiedzi) jest usuwane z normalnego przepływu wykonania. W twoim przykładzie natychmiast zwraca, a następna instrukcja, jest wykonywana przed funkcją, którą przekazałeś jako wywołanie zwrotne.$.ajax
return result;
success
Oto analogia, która, miejmy nadzieję, czyni różnicę między przepływem synchronicznym i asynchronicznym:
Synchroniczny
Wyobraź sobie, że dzwonisz do przyjaciela i prosisz go, aby coś dla ciebie poszukał. Chociaż może to chwilę potrwać, czekasz przez telefon i wpatrywasz się w kosmos, aż znajomy udzieli odpowiedzi, której potrzebujesz.
To samo dzieje się, gdy wykonujesz wywołanie funkcji zawierające „normalny” kod:
function findItem() {
var item;
while(item_not_found) {
// search
}
return item;
}
var item = findItem();
// Do something with item
doSomethingElse();
Mimo że wykonanie findItem
może potrwać długo, każdy następny kod var item = findItem();
musi poczekać, aż funkcja zwróci wynik.
Asynchroniczny
Z tego samego powodu ponownie dzwonisz do swojego przyjaciela. Ale tym razem powiesz mu, że się spieszysz i powinien oddzwonić na twój telefon komórkowy. Rozłączasz się, wychodzisz z domu i robisz wszystko, co planujesz. Gdy znajomy oddzwoni, masz do czynienia z informacjami, które ci przekazał.
Dokładnie tak się dzieje, kiedy wykonujesz żądanie Ajax.
findItem(function(item) {
// Do something with item
});
doSomethingElse();
Zamiast czekać na odpowiedź, wykonywanie jest kontynuowane natychmiast, a instrukcja jest wykonywana po wywołaniu Ajax. Aby ostatecznie uzyskać odpowiedź, udostępniasz funkcję, która ma być wywoływana po odebraniu odpowiedzi, oddzwanianie (zauważ coś? Oddzwonić ?). Wszelkie instrukcje przychodzące po tym wywołaniu są wykonywane przed wywołaniem wywołania zwrotnego.
Rozwiązania)
Uwzględnij asynchroniczną naturę JavaScript! Chociaż niektóre operacje asynchroniczne zapewniają synchroniczne odpowiedniki (podobnie jak „Ajax”), generalnie odradza się ich używanie, szczególnie w kontekście przeglądarki.
Dlaczego tak źle pytasz?
JavaScript działa w wątku interfejsu użytkownika przeglądarki, a każdy długotrwały proces zablokuje interfejs użytkownika, co spowoduje, że przestanie on odpowiadać. Dodatkowo istnieje górny limit czasu wykonywania dla JavaScript, a przeglądarka zapyta użytkownika, czy kontynuować wykonywanie, czy nie.
Wszystko to jest naprawdę złe doświadczenie użytkownika. Użytkownik nie będzie w stanie stwierdzić, czy wszystko działa dobrze, czy nie. Ponadto efekt będzie gorszy dla użytkowników z wolnym połączeniem.
Poniżej przyjrzymy się trzem różnym rozwiązaniom, które wszystkie budują jeden na drugim:
async/await
Obiecuje z (ES2017 +, dostępny w starszych przeglądarkach, jeśli używasz transpilatora lub regeneratora)
- Callbacki (popularne w węźle)
then()
Obiecuje za pomocą (ES2015 +, dostępny w starszych przeglądarkach, jeśli korzystasz z jednej z wielu bibliotek obietnic)
Wszystkie trzy są dostępne w obecnych przeglądarkach, a węzeł 7+.
Wersja ECMAScript wydana w 2017 r. Wprowadziła obsługę funkcji asynchronicznych na poziomie składni . Za pomocą async
i await
możesz pisać asynchronicznie w „stylu synchronicznym”. Kod jest nadal asynchroniczny, ale łatwiej go odczytać / zrozumieć.
async/await
opiera się na obietnicach: async
funkcja zawsze zwraca obietnicę. await
„rozpakowuje” obietnicę i albo powoduje, że wartość przyrzeczenia została rozwiązana, albo zgłasza błąd, jeśli obietnica została odrzucona.
Ważne: Możesz używać tylko await
wewnątrz async
funkcji. W tej chwili najwyższy poziom await
nie jest jeszcze obsługiwany, więc może być konieczne utworzenie asynchronicznego IIFE ( Natychmiastowe wywołanie wyrażenia funkcji ), aby rozpocząć async
kontekst.
Możesz przeczytać więcej o MDN async
i await
na jego temat.
Oto przykład, który opiera się na opóźnieniu powyżej:
// Using 'superagent' which will return a promise.
var superagent = require('superagent')
// This is isn't declared as `async` because it already returns a promise
function delay() {
// `delay` returns a promise
return new Promise(function(resolve, reject) {
// Only `delay` is able to resolve or reject the promise
setTimeout(function() {
resolve(42); // After 3 seconds, resolve the promise with value 42
}, 3000);
});
}
async function getAllBooks() {
try {
// GET a list of book IDs of the current user
var bookIDs = await superagent.get('/user/books');
// wait for 3 seconds (just for the sake of this example)
await delay();
// GET information about each book
return await superagent.get('/books/ids='+JSON.stringify(bookIDs));
} catch(error) {
// If any of the awaited promises was rejected, this catch block
// would catch the rejection reason
return null;
}
}
// Start an IIFE to use `await` at the top level
(async function(){
let books = await getAllBooks();
console.log(books);
})();
Aktualne przeglądarek i węzłów wersje obsługują async/await
. Możesz również obsługiwać starsze środowiska, przekształcając swój kod do ES5 za pomocą regeneratora (lub narzędzi korzystających z regeneratora, takich jak Babel ).
Niech funkcje akceptują oddzwanianie
Oddzwonienie to po prostu funkcja przekazana do innej funkcji. Ta inna funkcja może wywołać funkcję przekazaną, ilekroć jest gotowa. W kontekście procesu asynchronicznego wywołanie zwrotne będzie wywoływane za każdym razem, gdy proces asynchroniczny zostanie wykonany. Zwykle wynik jest przekazywany do wywołania zwrotnego.
W przykładzie pytania możesz foo
zaakceptować oddzwonienie i użyć go jako success
oddzwonienia. Więc to
var result = foo();
// Code that depends on 'result'
staje się
foo(function(result) {
// Code that depends on 'result'
});
Tutaj zdefiniowaliśmy funkcję „inline”, ale możesz przekazać dowolne odwołanie do funkcji:
function myCallback(result) {
// Code that depends on 'result'
}
foo(myCallback);
foo
sam jest zdefiniowany w następujący sposób:
function foo(callback) {
$.ajax({
// ...
success: callback
});
}
callback
będzie odnosić się do funkcji, którą przekazujemy, foo
kiedy ją wywołujemy i po prostu ją przekazujemy success
. To znaczy, gdy żądanie Ajax zakończy się powodzeniem, $.ajax
zadzwoni callback
i przekaże odpowiedź do wywołania zwrotnego (do którego można się odwołać result
, ponieważ w ten sposób zdefiniowaliśmy wywołanie zwrotne).
Możesz również przetworzyć odpowiedź przed przekazaniem jej do wywołania zwrotnego:
function foo(callback) {
$.ajax({
// ...
success: function(response) {
// For example, filter the response
callback(filtered_response);
}
});
}
Łatwiej jest pisać kod za pomocą wywołań zwrotnych, niż może się wydawać. W końcu JavaScript w przeglądarce jest silnie sterowany zdarzeniami (zdarzenia DOM). Otrzymanie odpowiedzi Ajax to nic innego jak wydarzenie.
Problemy mogą pojawić się, gdy trzeba pracować z kodem innej firmy, ale większość problemów można rozwiązać, po prostu analizując przepływ aplikacji.
ES2015 +: Obiecuje za pomocą then ()
Obietnica API jest nowa funkcja ECMAScript 6 (ES2015), ale ma dobrą obsługę przeglądarki już. Istnieje również wiele bibliotek, które implementują standardowy interfejs API Promises i zapewniają dodatkowe metody ułatwiające korzystanie z funkcji asynchronicznych i ich komponowanie (np. Bluebird ).
Obietnice są pojemnikami na przyszłe wartości. Kiedy obietnica otrzyma wartość (zostanie rozwiązana ) lub gdy zostanie anulowana ( odrzucona ), powiadomi wszystkich swoich „słuchaczy”, którzy chcą uzyskać dostęp do tej wartości.
Zaletą zwykłych wywołań zwrotnych jest to, że pozwalają one oddzielić kod i są łatwiejsze do skomponowania.
Oto prosty przykład użycia obietnicy:
function delay() {
// `delay` returns a promise
return new Promise(function(resolve, reject) {
// Only `delay` is able to resolve or reject the promise
setTimeout(function() {
resolve(42); // After 3 seconds, resolve the promise with value 42
}, 3000);
});
}
delay()
.then(function(v) { // `delay` returns a promise
console.log(v); // Log the value once it is resolved
})
.catch(function(v) {
// Or do something else if it is rejected
// (it would not happen in this example, since `reject` is not called).
});
W przypadku naszego połączenia Ajax możemy użyć takich obietnic:
function ajax(url) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
resolve(this.responseText);
};
xhr.onerror = reject;
xhr.open('GET', url);
xhr.send();
});
}
ajax("/echo/json")
.then(function(result) {
// Code depending on result
})
.catch(function() {
// An error occurred
});
Opisanie wszystkich korzyści, które obiecują, wykracza poza zakres tej odpowiedzi, ale jeśli napiszesz nowy kod, powinieneś poważnie je rozważyć. Zapewniają doskonałą abstrakcję i separację kodu.
Więcej informacji o obietnicach: skały HTML5 - obietnice JavaScript
Uwaga dodatkowa: odroczone obiekty jQuery
Odroczone obiekty są niestandardową implementacją obietnic jQuery (przed standaryzacją interfejsu Promise API). Zachowują się prawie jak obietnice, ale ujawniają nieco inny interfejs API.
Każda metoda Ajaks jQuery zwraca już „odroczony obiekt” (faktycznie obietnica odroczonego obiektu), który możesz po prostu zwrócić z funkcji:
function ajax() {
return $.ajax(...);
}
ajax().done(function(result) {
// Code depending on result
}).fail(function() {
// An error occurred
});
Uwaga dodatkowa: Obietnica gotchas
Pamiętaj, że obietnice i odroczone obiekty są tylko pojemnikami na przyszłą wartość, a nie samą wartością. Załóżmy na przykład, że masz:
function checkPassword() {
return $.ajax({
url: '/password',
data: {
username: $('#username').val(),
password: $('#password').val()
},
type: 'POST',
dataType: 'json'
});
}
if (checkPassword()) {
// Tell the user they're logged in
}
Ten kod źle rozumie powyższe problemy z asynchronią. W szczególności $.ajax()
nie zamraża kodu podczas sprawdzania strony „/ password” na serwerze - wysyła żądanie do serwera i podczas oczekiwania natychmiast zwraca obiekt jQuery Ajax Deferred, a nie odpowiedź z serwera. Oznacza to, że if
instrukcja będzie zawsze pobierać ten obiekt odroczony, traktować go jak true
i postępować tak, jakby użytkownik był zalogowany. Nie dobrze.
Ale poprawka jest łatwa:
checkPassword()
.done(function(r) {
if (r) {
// Tell the user they're logged in
} else {
// Tell the user their password was bad
}
})
.fail(function(x) {
// Tell the user something bad happened
});
Niezalecane: Synchroniczne wywołania „Ajax”
Jak wspomniałem, niektóre (!) Operacje asynchroniczne mają synchroniczne odpowiedniki. Nie opowiadam się za ich użyciem, ale ze względu na kompletność, oto jak możesz wykonać połączenie synchroniczne:
Bez jQuery
Jeśli bezpośrednio używasz XMLHTTPRequest
obiektu, przekaż false
jako trzeci argument do .open
.
jQuery
Jeśli używasz jQuery , możesz ustawić async
opcję na false
. Zauważ, że ta opcja jest przestarzała od jQuery 1.8. Następnie możesz albo użyć success
wywołania zwrotnego, albo uzyskać dostęp do responseText
właściwości obiektu jqXHR :
function foo() {
var jqXHR = $.ajax({
//...
async: false
});
return jqXHR.responseText;
}
Jeśli używasz innej metody Ajax jQuery, takiej jak $.get
, $.getJSON
itp., Musisz ją zmienić na $.ajax
(ponieważ możesz przekazać tylko parametry konfiguracyjne $.ajax
).
Heads-up! Nie można wykonać synchronicznego żądania JSONP . JSONP ze swej natury jest zawsze asynchroniczny (jeszcze jeden powód, aby nawet nie rozważać tej opcji).
If you use any other jQuery AJAX method, such as $.get, $.getJSON, etc., you have them to $.ajax.
(Tak, zdaję sobie sprawę, że mój nick jest odrobinę ironiczny w tym przypadku)foo
jest wywoływana, a funkcja jest do niej przekazywana (foo(function(result) {....});
)?result
jest używany wewnątrz tej funkcji i jest odpowiedzią na żądanie Ajax. Aby odwołać się do tej funkcji, pierwszy parametr foo jest wywoływanycallback
i przypisywanysuccess
zamiast funkcji anonimowej. Więc$.ajax
zadzwoni,callback
gdy żądanie zakończy się powodzeniem. Próbowałem to trochę wyjaśnić.$.getJSON
jeśli chcesz, aby żądanie Ajax było synchroniczne. Jednak nie powinieneś chcieć, aby żądanie było synchroniczne, więc to nie ma zastosowania. Powinieneś używać wywołań zwrotnych lub obietnic do obsługi odpowiedzi, jak wyjaśniono to wcześniej w odpowiedzi.Jeśli nie używasz jQuery w kodzie, ta odpowiedź jest dla Ciebie
Twój kod powinien być mniej więcej taki:
Felix Kling wykonał świetną robotę, pisząc odpowiedź dla osób używających jQuery dla AJAX, postanowiłem zapewnić alternatywę dla ludzi, którzy nie są.
( Uwaga: dla osób korzystających z nowego
fetch
API, Angulara lub obietnic dodałem inną odpowiedź poniżej )Co masz do czynienia?
To jest krótkie podsumowanie „Wyjaśnienia problemu” z drugiej odpowiedzi, jeśli nie jesteś pewien po przeczytaniu tego, przeczytaj to.
W AJAX oznacza asynchroniczny . Oznacza to, że wysłanie żądania (a raczej otrzymanie odpowiedzi) jest usuwane z normalnego przepływu wykonania. W twoim przykładzie natychmiast zwraca, a następna instrukcja, jest wykonywana przed funkcją, którą przekazałeś jako wywołanie zwrotne.
.send
return result;
success
Oznacza to, że gdy wracasz, zdefiniowany przez ciebie detektor jeszcze się nie uruchomił, co oznacza, że zwracana wartość nie została zdefiniowana.
Oto prosta analogia
(Skrzypce)
Wartość
a
zwracana jest,undefined
ponieważa=5
część nie została jeszcze wykonana. AJAX działa w ten sposób, zwracasz wartość, zanim serwer będzie miał szansę powiedzieć przeglądarce, co to jest.Jednym z możliwych rozwiązań tego problemu jest ponowne aktywne kodowanie , informujące program, co ma robić po zakończeniu obliczeń.
To się nazywa CPS . Zasadniczo przekazujemy
getFive
akcję do wykonania po jej zakończeniu, mówimy naszemu kodowi, jak zareagować po zakończeniu zdarzenia (jak nasze wywołanie AJAX lub w tym przypadku limit czasu).Wykorzystanie byłoby:
Co powinno ostrzec „5” na ekranie. (Skrzypce) .
Możliwe rozwiązania
Istnieją zasadniczo dwa sposoby rozwiązania tego problemu:
1. Synchroniczny AJAX - nie rób tego !!
Jeśli chodzi o synchroniczny AJAX, nie rób tego! Odpowiedź Felixa podnosi kilka przekonujących argumentów na temat tego, dlaczego jest to zły pomysł. Podsumowując, zawiesi przeglądarkę użytkownika, dopóki serwer nie zwróci odpowiedzi i spowoduje bardzo złe wrażenia użytkownika. Oto kolejne krótkie podsumowanie zaczerpnięte z MDN na temat tego, dlaczego:
Jeśli mają to zrobić, można przekazać flagi: Oto jak to zrobić:
2. Kod restrukturyzacji
Pozwól swojej funkcji zaakceptować oddzwonienie. W tym przykładzie
foo
można wprowadzić kod, aby akceptować oddzwonienie. Powiemy naszemu kodowi, jak zareagować pofoo
zakończeniu.Więc:
Staje się:
Przekazaliśmy tutaj funkcję anonimową, ale równie łatwo moglibyśmy przekazać odwołanie do istniejącej funkcji, dzięki czemu wygląda ona następująco:
Aby uzyskać więcej informacji o tym, jak tego rodzaju projektowanie połączeń zwrotnych jest wykonywane, sprawdź odpowiedź Felixa.
Teraz zdefiniujmy sam foo, aby działał odpowiednio
(skrzypce)
Teraz sprawiliśmy, że nasza funkcja foo akceptuje działanie, które zostanie uruchomione, gdy AJAX zakończy się pomyślnie, możemy to rozszerzyć, sprawdzając, czy status odpowiedzi nie ma wartości 200 i działając odpowiednio (utwórz moduł obsługi błędów i tym podobne). Skutecznie rozwiązujemy nasz problem.
Jeśli nadal masz trudności ze zrozumieniem tego, przeczytaj AJAX przewodnik dla początkujących w MDN.
źródło
onload
obsługi, który uruchamia się tylko wtedy, gdyreadyState
jest4
. Oczywiście nie jest obsługiwany w IE8. (iirc, może wymagać potwierdzenia.)XMLHttpRequest 2 (przede wszystkim przeczytaj odpowiedzi Benjamina Gruenbauma i Felixa Klinga )
Jeśli nie używasz jQuery i chcesz mieć ładny krótki XMLHttpRequest 2, który działa w nowoczesnych przeglądarkach, a także w przeglądarkach mobilnych, sugeruję użyć go w ten sposób:
Jak widzisz:
Istnieją dwa sposoby uzyskania odpowiedzi na to wywołanie Ajax (trzy przy użyciu nazwy zmiennej XMLHttpRequest):
Najprostszy:
Lub jeśli z jakiegoś powodu jesteś
bind()
oddzwanianiem do klasy:Przykład:
Lub (powyższe jest lepsze, anonimowe funkcje zawsze stanowią problem):
Nic prostszego.
Teraz niektórzy ludzie zapewne powiedzą, że lepiej jest użyć onreadystatechange lub nawet nazwy zmiennej XMLHttpRequest. To jest źle.
Sprawdź zaawansowane funkcje XMLHttpRequest
Obsługuje wszystkie * nowoczesne przeglądarki. I mogę potwierdzić, ponieważ używam tego podejścia, ponieważ istnieje XMLHttpRequest 2. Nigdy nie miałem żadnego problemu we wszystkich przeglądarkach, z których korzystam.
onreadystatechange jest przydatny tylko wtedy, gdy chcesz uzyskać nagłówki w stanie 2.
Używanie
XMLHttpRequest
nazwy zmiennej to kolejny duży błąd, ponieważ musisz wykonać wywołanie zwrotne wewnątrz zamknięć onload / oreadystatechange, w przeciwnym razie ją utracisz.Teraz, jeśli chcesz czegoś bardziej złożonego za pomocą post i FormData, możesz łatwo rozszerzyć tę funkcję:
Znowu ... jest to bardzo krótka funkcja, ale pobiera i wysyła.
Przykłady użycia:
Lub przekaż pełny element form (
document.getElementsByTagName('form')[0]
):Lub ustaw wartości niestandardowe:
Jak widać, nie wdrożyłem synchronizacji ... to zła rzecz.
Powiedziawszy to ... dlaczego nie zrobić tego w prosty sposób?
Jak wspomniano w komentarzu, użycie error && synchronous całkowicie łamie sens odpowiedzi. Jaka jest dobra krótka droga do właściwego korzystania z Ajax?
Obsługa błędów
W powyższym skrypcie masz moduł obsługi błędów, który jest statycznie zdefiniowany, aby nie wpływał na działanie funkcji. Program obsługi błędów może być również używany do innych funkcji.
Ale aby naprawdę wyjść z błędu, jedynym sposobem jest napisanie niewłaściwego adresu URL, w którym to przypadku każda przeglądarka zgłasza błąd.
Procedury obsługi błędów mogą być przydatne, jeśli ustawisz niestandardowe nagłówki, ustaw właściwość responseType na bufor tablicy obiektów blob lub cokolwiek ...
Nawet jeśli podasz metodę „POSTAPAPAP” jako metodę, nie spowoduje to błędu.
Nawet jeśli podasz „fdggdgilfdghfldj” jako dane formalne, nie spowoduje to błędu.
W pierwszym przypadku błąd znajduje się wewnątrz
displayAjax()
podthis.statusText
jakoMethod not Allowed
.W drugim przypadku po prostu działa. Musisz sprawdzić po stronie serwera, czy przekazałeś odpowiednie dane postu.
niedozwolone w wielu domenach automatycznie generuje błąd.
W odpowiedzi na błąd nie ma kodów błędów.
Jest tylko to,
this.type
co jest ustawione na błąd.Po co dodawać moduł obsługi błędów, jeśli całkowicie nie ma się kontroli nad błędami? Większość błędów jest zwracanych w tym funkcji zwrotnej
displayAjax()
.Więc: Nie ma potrzeby sprawdzania błędów, jeśli możesz poprawnie skopiować i wkleić adres URL. ;)
PS: Jako pierwszy test napisałem x ('x', displayAjax) ... i całkowicie otrzymałem odpowiedź ... ??? Sprawdziłem więc folder, w którym znajduje się HTML, i znalazłem plik o nazwie „x.xml”. Więc nawet jeśli zapomnisz rozszerzenia swojego pliku XMLHttpRequest 2, ZNAJDUJE SIĘ . LOL'd
Czytaj plik synchroniczny
Nie rób tego
Jeśli chcesz zablokować przeglądarkę na jakiś czas, załaduj ładny duży
.txt
plik synchroniczny.Teraz możesz zrobić
Nie ma innego sposobu na zrobienie tego w sposób niesynchroniczny. (Tak, z pętlą setTimeout ... ale poważnie?)
Kolejną kwestią jest ... jeśli pracujesz z interfejsami API lub tylko plikami własnej listy lub czymkolwiek, zawsze używasz różnych funkcji dla każdego żądania ...
Tylko jeśli masz stronę, na której zawsze ładujesz ten sam kod XML / JSON lub cokolwiek, czego potrzebujesz tylko jedną funkcję. W takim przypadku zmodyfikuj trochę funkcję Ajax i zastąp b funkcją specjalną.
Powyższe funkcje są do podstawowego użytku.
Jeśli chcesz PRZEDŁUŻYĆ funkcję ...
Tak, możesz.
Korzystam z wielu interfejsów API, a jedną z pierwszych funkcji, które integruję na każdej stronie HTML, jest pierwsza funkcja Ajax w tej odpowiedzi, tylko z GET ...
Ale możesz zrobić wiele rzeczy z XMLHttpRequest 2:
Zrobiłem menedżera pobierania (używając zakresów po obu stronach z CV, lidera plików, systemu plików), różnych konwerterów resizera obrazu za pomocą płótna, wypełniania internetowych baz danych SQL obrazami base64 i wiele więcej ... Ale w takich przypadkach powinieneś utworzyć funkcję tylko do tego cel ... czasami potrzebujesz obiektu blob, buforów macierzy, możesz ustawić nagłówki, przesłonić typ MIME i jest o wiele więcej ...
Ale pytanie tutaj brzmi: jak zwrócić odpowiedź Ajax ... (dodałem prosty sposób).
źródło
x
,ajax
lubxhr
może być ładniejszy :)). Nie widzę, w jaki sposób odnosi się do zwracania odpowiedzi z połączenia AJAX. (ktoś nadal może to zrobićvar res = x("url")
i nie rozumie, dlaczego to nie działa;)). Na marginesie - byłoby fajnie, gdybyś wróciłc
z tej metody, aby użytkownicy mogli się przyczepićerror
itp.2.ajax is meant to be async.. so NO var res=x('url')..
To jest sedno tego pytania i odpowiedzi :)Jeśli korzystasz z obietnic, ta odpowiedź jest dla Ciebie.
Oznacza to AngularJS, jQuery (z odroczeniem), zastępowanie natywnego XHR (pobieranie), EmberJS, zapisywanie BackboneJS lub dowolną bibliotekę węzłów, która zwraca obietnice.
Twój kod powinien być mniej więcej taki:
Felix Kling wykonał świetną robotę, pisząc odpowiedź dla osób używających jQuery z wywołaniami zwrotnymi dla AJAX. Mam odpowiedź dla natywnego XHR. Ta odpowiedź dotyczy ogólnego użycia obietnic na interfejsie użytkownika lub na zapleczu.
Podstawowy problem
Model współbieżności JavaScript w przeglądarce i na serwerze z NodeJS / io.js jest asynchroniczny i reaktywny .
Za każdym razem, gdy wywołujesz metodę, która zwraca obietnicę, procedury
then
obsługi są zawsze wykonywane asynchronicznie - to znaczy po kodzie poniżej nich, który nie jest w.then
module obsługi.Znaczy to, że wracasz
data
dothen
obsługi nie masz zdefiniowanego jeszcze nie wykonać. To z kolei oznacza, że zwracana wartość nie została ustawiona na prawidłową wartość w czasie.Oto prosta analogia do problemu:
Wartość
data
wynosi,undefined
ponieważdata = 5
część nie została jeszcze wykonana. Prawdopodobnie wykona się za sekundę, ale do tego czasu nie ma znaczenia dla zwracanej wartości.Ponieważ operacja jeszcze się nie wydarzyła (AJAX, połączenie z serwerem, IO, timer), zwracasz wartość, zanim żądanie dostanie szansę, by powiedzieć Twojemu kodowi, jaka jest ta wartość.
Jednym z możliwych rozwiązań tego problemu jest ponowne aktywne kodowanie , informujące program, co ma robić po zakończeniu obliczeń. Obietnice aktywnie to umożliwiają, ponieważ mają charakter czasowy (wrażliwy na czas).
Szybkie podsumowanie obietnic
Obietnica jest wartością w czasie . Obietnice mają swój stan, zaczynają się jako oczekujące bez wartości i mogą zostać rozliczone na:
Obietnica może zmienić stan tylko raz, po czym zawsze pozostanie w tym samym stanie na zawsze. Możesz dołączyć programy
then
obsługi do obietnic, aby wydobyć ich wartość i obsłużyć błędy.then
teleskopowe umożliwiają łączenia rozmów. Obietnice są tworzone przy użyciu interfejsów API, które je zwracają . Na przykład, bardziej nowoczesna zamiana AJAXfetch
lub$.get
obietnica zwrotu jQuery .Kiedy zadzwonić
.then
na obietnicy i powrotnej coś z tym - mamy obietnicę dla przetworzonego wartości . Jeśli zwrócimy kolejną obietnicę, otrzymamy niesamowite rzeczy, ale trzymajmy nasze konie.Z obietnicami
Zobaczmy, jak możemy rozwiązać powyższy problem za pomocą obietnic. Po pierwsze, pokażmy nasze zrozumienie stanów obietnicy z góry, używając konstruktora Promise do utworzenia funkcji opóźnienia:
Teraz, po przekonwertowaniu setTimeout do korzystania z obietnic, możemy użyć,
then
aby liczyć:Zasadniczo, zamiast wrócić do wartości , której nie możemy zrobić, ponieważ model współbieżności - wracamy do otoki dla wartości, które możemy rozpakować z
then
. To jest jak pudełko, które można otworzyćthen
.Stosuję to
To samo dotyczy Twojego oryginalnego wywołania API, możesz:
Więc to działa równie dobrze. Dowiedzieliśmy się, że nie możemy zwracać wartości z już asynchronicznych wywołań, ale możemy używać obietnic i łączyć je w celu przetwarzania. Wiemy już, jak zwrócić odpowiedź z połączenia asynchronicznego.
ES2015 (ES6)
ES6 wprowadza generatory, które są funkcjami, które mogą powrócić na środku, a następnie wznowić od momentu, w którym były. Jest to zwykle przydatne w przypadku sekwencji, na przykład:
Jest funkcją, która zwraca iterator nad sekwencją,
1,2,3,3,3,3,....
którą można iterować. Chociaż jest to interesujące samo w sobie i otwiera wiele możliwości, jest jeden szczególnie interesujący przypadek.Jeśli tworzona przez nas sekwencja jest sekwencją akcji, a nie liczb - możemy wstrzymać funkcję, ilekroć akcja zostanie wykonana, i poczekać na nią, zanim wznowimy funkcję. Zamiast sekwencji liczb potrzebujemy sekwencji przyszłych wartości - to znaczy: obietnic.
Ta nieco trudna, ale bardzo skuteczna sztuczka pozwala nam pisać kod asynchroniczny w sposób synchroniczny. Jest kilku „biegaczy”, którzy robią to za Ciebie, napisanie jednego to kilka krótkich linii kodu, ale wykracza poza zakres tej odpowiedzi. Będę tu używał Bluebird
Promise.coroutine
, ale są też inne opakowania, takie jakco
lubQ.async
.Ta metoda sama w sobie zwraca obietnicę, którą możemy spożywać z innych koroutyn. Na przykład:
ES2016 (ES7)
W ES7 jest to jeszcze bardziej znormalizowane, jest teraz kilka propozycji, ale we wszystkich z nich możesz
await
obiecać. To jest po prostu „cukier” (ładniejsza składnia) dla powyższej propozycji ES6 poprzez dodanie słów kluczowychasync
iawait
. Robiąc powyższy przykład:Nadal zwraca obietnicę tak samo :)
źródło
return await data.json();
?)Używasz Ajax niepoprawnie. Chodzi o to, aby nie zwracać niczego, ale zamiast tego przekazać dane do czegoś, co nazywa się funkcją wywołania zwrotnego, która obsługuje dane.
To jest:
Zwracanie czegokolwiek w module obsługi przesyłania nic nie robi. Zamiast tego musisz przekazać dane lub zrobić to, co chcesz, bezpośrednio w funkcji sukcesu.
źródło
success: handleData
i to zadziała.success
metodą, jest to otaczający zakres$.ajax
.Najprostszym rozwiązaniem jest utworzenie funkcji JavaScript i wywołanie jej dla
success
wywołania zwrotnego Ajax .źródło
Odpowiem okropnie wyglądającym, ręcznie rysowanym komiksem. Drugi obraz jest powodem, dla którego
result
znajduje sięundefined
w twoim kodzie przykład.źródło
Kątowy 1
Dla osób korzystających z AngularJS , można poradzić sobie z tą sytuacją przy użyciu
Promises
.Tutaj mówi:
Można znaleźć ładne wyjaśnienie tutaj również.
Przykład znaleziony w dokumentach wymienionych poniżej.
Angular2 i późniejsze
W
Angular2
ze spojrzeniem na poniższym przykładzie, ale jego zalecany do stosowaniaObservables
zAngular2
.}
Możesz to wykorzystać w ten sposób,
Zobacz oryginalny post tutaj. Ale Maszynopis nie obsługuje natywnych obietnic es6 , jeśli chcesz go użyć, możesz potrzebować do tego wtyczki.
Dodatkowo tutaj są określone specyfikacje obietnic .
źródło
Większość odpowiedzi tutaj zawiera przydatne sugestie dotyczące pojedynczej operacji asynchronicznej, ale czasami pojawia się, gdy trzeba wykonać operację asynchroniczną dla każdej pozycji w tablicy lub innej strukturze podobnej do listy. Pokusa polega na tym, aby to zrobić:
Przykład:
Pokaż fragment kodu
Powodem, dla którego nie działa, jest to, że wywołania zwrotne z
doSomethingAsync
jeszcze nie zostały uruchomione, zanim spróbujesz użyć wyników.Tak więc, jeśli masz tablicę (lub jakąś listę) i chcesz wykonać operacje asynchroniczne dla każdego wpisu, masz dwie opcje: Wykonaj operacje równolegle (nakładające się) lub szeregowo (jedna po drugiej w sekwencji).
Równolegle
Możesz uruchomić je wszystkie i śledzić liczbę oczekiwanych oddzwonień, a następnie użyć wyników, gdy otrzymujesz tyle oddzwonień:
Przykład:
Pokaż fragment kodu
(Moglibyśmy pozbyć się
expecting
i po prostu użyćresults.length === theArray.length
, ale to pozostawia nas otwartymi na możliwość, któratheArray
jest zmieniana, gdy połączenia są zaległe ...)Zwróć uwagę, w jaki sposób używamy
index
from od,forEach
aby zapisać wynik wresults
tej samej pozycji, co wpis, którego dotyczy, nawet jeśli wyniki pojawiają się poza kolejnością (ponieważ wywołania asynchroniczne niekoniecznie kończą się w kolejności, w której zostały uruchomione).Ale co, jeśli chcesz zwrócić te wyniki z funkcji? Jak wskazały inne odpowiedzi, nie możesz; musisz zaakceptować funkcję i oddzwonić (lub zwrócić obietnicę ). Oto wersja oddzwaniania:
Przykład:
Pokaż fragment kodu
Lub oto wersja zwracająca
Promise
zamiast tego:Oczywiście, jeśli
doSomethingAsync
przekażą nam błędy,reject
odrzucilibyśmy obietnicę, gdy dostaliśmy błąd).Przykład:
Pokaż fragment kodu
(Lub możesz zrobić opakowanie,
doSomethingAsync
które zwróci obietnicę, a następnie wykonaj poniższe czynności ...)Jeśli
doSomethingAsync
dajesz obietnicę , możesz użyćPromise.all
:Jeśli wiesz, że
doSomethingAsync
zignoruje drugi i trzeci argument, możesz po prostu przekazać go bezpośredniomap
(map
wywołuje funkcję zwrotną za pomocą trzech argumentów, ale większość ludzi używa tylko pierwszego).Przykład:
Pokaż fragment kodu
Zauważ, że
Promise.all
rozwiązuje swoją obietnicę za pomocą szeregu wyników wszystkich obietnic, które dajesz, gdy wszystkie zostaną rozpatrzone, lub odrzuca obietnicę, gdy pierwsza z obietnic, które dajesz, odrzuca.Seria
Załóżmy, że nie chcesz, aby operacje były równoległe? Jeśli chcesz je uruchamiać jeden po drugim, musisz poczekać na zakończenie każdej operacji przed rozpoczęciem następnej. Oto przykład funkcji, która to robi i wywołuje wywołanie zwrotne z wynikiem:
(Ponieważ wykonujemy pracę szeregowo, możemy po prostu użyć,
results.push(result)
ponieważ wiemy, że nie uzyskamy wyników poza kolejnością. W powyższym przypadku mogliśmy zastosowaćresults[index] = result;
, ale w niektórych z poniższych przykładów nie mamy indeksu używać.)Przykład:
Pokaż fragment kodu
(Lub ponownie zbuduj opakowanie,
doSomethingAsync
które daje ci obietnicę i wykonaj poniższe czynności ...)Jeśli
doSomethingAsync
daje obietnicę, jeśli możesz użyć składni ES2017 + (być może z transpilerem takim jak Babel ), możesz użyćasync
funkcji za pomocąfor-of
iawait
:Przykład:
Pokaż fragment kodu
Jeśli nie możesz użyć składni ES2017 + (jeszcze), możesz użyć wariacji na wzorzec „Obietnica zmniejszenia” (jest to bardziej złożone niż zwykłe zmniejszenie Obietnicy, ponieważ nie przekazujemy wyniku z jednego do drugiego, ale zamiast tego gromadzenie ich wyników w tablicy):
Przykład:
Pokaż fragment kodu
... co jest mniej kłopotliwe dzięki funkcjom strzałek ES2015 + :
Przykład:
Pokaż fragment kodu
źródło
if (--expecting === 0)
część kodu? Wersja oddzwaniania twojego rozwiązania działa dla mnie świetnie, po prostu nie rozumiem, w jaki sposób dzięki temu stwierdzeniu sprawdzasz liczbę ukończonych odpowiedzi. Doceń to po prostu brak wiedzy z mojej strony. Czy istnieje alternatywny sposób na napisanie czeku?expecting
zaczyna się od wartościarray.length
, czyli liczby żądań, które złożymy. Wiemy, że oddzwonienie nie zostanie wywołane, dopóki wszystkie te żądania nie zostaną uruchomione. W wywołaniu zwrotnymif (--expecting === 0)
robi to: 1. Dekrementacjeexpecting
(otrzymaliśmy odpowiedź, więc oczekujemy o jeden mniej odpowiedzi) i jeśli wartość po dekrementacji wynosi 0 (nie oczekujemy żadnych odpowiedzi), to gotowy!results
nie istniał). :-) Naprawione.Spójrz na ten przykład:
Jak widać
getJoke
jest zwrócenie rozwiązany obietnicę (nie zostanie rozwiązany po powrocieres.data.value
). Poczekaj więc, aż żądanie $ http.get zostanie zakończone, a następnie zostanie uruchomiony plik console.log (res.joke) (jako normalny przepływ asynchroniczny).To jest plnkr:
http://embed.plnkr.co/XlNR7HpCaIhJxskMJfSg/
Sposób ES6 (asynchronizacja - oczekiwanie)
źródło
Jest to jedno z miejsc, w których dwa sposoby wiązania danych lub przechowywania danych, które są używane w wielu nowych ramach JavaScript, będą dla Ciebie świetne ...
Więc jeśli używasz Angulara, Reacta lub jakiegokolwiek innego frameworka, który łączy dwa sposoby wiązania lub przechowywania danych, ten problem jest po prostu dla Ciebie naprawiony, więc jednym słowem, twój wynik jest
undefined
na pierwszym etapie, więc maszresult = undefined
przed otrzymaniem dane, a następnie, gdy tylko otrzymasz wynik, zostanie on zaktualizowany i zostanie przypisany do nowej wartości, która odpowiada na wywołanie Ajax ...Ale jak możesz to zrobić na przykład w javascript lub jQuery, tak jak zadałeś to pytanie?
Możesz użyć funkcji zwrotnej , obietnicy i ostatnio obserwowalnej, aby obsłużyć ją za Ciebie, na przykład w obietnicach mamy jakąś funkcję, taką jak
success()
lub,then()
która zostanie wykonana, gdy Twoje dane będą dla Ciebie gotowe, to samo z funkcją zwrotną lub subskrypcją na obserwowalnym .Na przykład w twoim przypadku, w którym używasz jQuery , możesz zrobić coś takiego:
Aby uzyskać więcej informacji, zapoznaj się z obietnicami i obserwowalnościami, które są nowszymi sposobami wykonywania tego asynchronicznego działania.
źródło
$.ajax({url: "api/data", success: fooDone.bind(this)});
Jest to bardzo powszechny problem, z którym zmagamy się, zmagając się z „tajemnicami” JavaScript. Pozwólcie mi dzisiaj spróbować wyjaśnić tę tajemnicę.
Zacznijmy od prostej funkcji JavaScript:
Jest to proste wywołanie funkcji synchronicznej (gdzie każdy wiersz kodu jest „zakończony swoim zadaniem” przed kolejnym w sekwencji), a wynik jest taki sam, jak oczekiwano.
Dodajmy teraz nieco zwrotów akcji, wprowadzając niewielkie opóźnienie w naszej funkcji, aby wszystkie wiersze kodu nie były „gotowe” w sekwencji. W ten sposób będzie emulować asynchroniczne zachowanie funkcji:
Proszę bardzo, to opóźnienie po prostu zepsuło oczekiwaną funkcjonalność! Ale co dokładnie się stało? Cóż, jest to całkiem logiczne, jeśli spojrzysz na kod. funkcja
foo()
po wykonaniu nic nie zwraca (w ten sposób zwracana jest wartośćundefined
), ale uruchamia stoper, który wykonuje funkcję po 1 s, aby zwrócić „wohoo”. Ale jak widać, wartość przypisana do baru to natychmiast zwrócone rzeczy z foo (), co nie jest niczym, to jest po prostuundefined
.Jak więc rozwiązać ten problem?
Zapytajmy naszą funkcję o OBIETNICA . Obietnica tak naprawdę chodzi o to, co to znaczy: oznacza, że funkcja gwarantuje, że zapewnisz wszelkie dane wyjściowe, które otrzyma w przyszłości. zobaczmy więc, jak działa nasz mały problem powyżej:
Tak więc podsumowanie jest następujące: aby poradzić sobie z funkcjami asynchronicznymi, takimi jak wywołania ajax itp., Możesz użyć obietnicy
resolve
wartości (którą zamierzasz zwrócić). Krótko mówiąc, rozwiązujesz wartość zamiast zwracać , w funkcjach asynchronicznych.AKTUALIZACJA (obiecuje asynchronicznie / oczekuj)
Oprócz
then/catch
pracy z obietnicami istnieje jeszcze jedno podejście. Chodzi o to, aby rozpoznać funkcję asynchroniczną, a następnie poczekać na spełnienie obietnic przed przejściem do następnego wiersza kodu. To wciąż tylkopromises
pod maską, ale z innym podejściem syntaktycznym. Aby wszystko było bardziej zrozumiałe, możesz znaleźć porównanie poniżej:następnie wersja / catch:
async / oczekiwanie na wersję:
źródło
Innym podejściem do zwracania wartości z funkcji asynchronicznej jest przekazanie obiektu, który zapisze wynik funkcji asynchronicznej.
Oto przykład tego samego:
Korzystam z
result
obiektu do przechowywania wartości podczas operacji asynchronicznej. Dzięki temu wynik będzie dostępny nawet po zadaniu asynchronicznym.Często używam tego podejścia. Byłbym zainteresowany, aby dowiedzieć się, jak dobrze to podejście działa, gdy w grę wchodzi podłączenie wyniku z powrotem przez kolejne moduły.
źródło
result
. Działa, ponieważ odczytujesz zmienną po zakończeniu funkcji asynchronicznej.Podczas gdy obietnice i oddzwaniania działają dobrze w wielu sytuacjach, trudno jest wyrazić coś z tyłu:
Skończysz przechodzić
async1
; sprawdź, czyname
jest niezdefiniowany, czy nie i odpowiednio oddzwoń.Chociaż w małych przykładach jest to w porządku , denerwuje się, gdy masz wiele podobnych przypadków i obsługi błędów.
Fibers
pomaga w rozwiązaniu problemu.Możesz sprawdzić projekt tutaj .
źródło
async-await
jeśli korzystasz z niektórych najnowszych wersji węzła. Jeśli ktoś utknął w starszych wersjach, może skorzystać z tej metody.Poniższy przykład, który napisałem, pokazuje, jak to zrobić
Ten działający przykład jest samodzielny. Zdefiniuje prosty obiekt żądania, który używa
XMLHttpRequest
obiektu okna do wykonywania połączeń. Zdefiniuje prostą funkcję oczekiwania na wypełnienie szeregu obietnic.Kontekst. Przykładem jest zapytanie do punktu końcowego Spotify Web API w celu wyszukania
playlist
obiektów dla danego zestawu ciągów zapytań:Dla każdego elementu nowa obietnica uruchomi blok -
ExecutionBlock
przeanalizuj wynik, zaplanuj nowy zestaw obietnic w oparciu o tablicę wyników, czyli listęuser
obiektów Spotify i wykonajExecutionProfileBlock
asynchronicznie nowe wywołanie HTTP .Następnie możesz zobaczyć zagnieżdżoną strukturę Promise, która pozwala spawnować wiele całkowicie asynchronicznych zagnieżdżonych wywołań HTTP i dołączać wyniki z każdego podzbioru wywołań
Promise.all
.UWAGA Najnowsze
search
interfejsy API Spotify będą wymagały podania tokena dostępu w nagłówkach żądania:Aby uruchomić następujący przykład, musisz umieścić token dostępu w nagłówkach żądania:
Mam obszernie omawiane rozwiązanie to tutaj .
źródło
Krótka odpowiedź brzmi: musisz zaimplementować funkcję oddzwaniania:
źródło
Odpowiedź 2017: możesz teraz robić dokładnie to, co chcesz w każdej bieżącej przeglądarce i węźle
To jest dość proste:
Oto działająca wersja Twojego kodu:
Oczekiwanie jest obsługiwane we wszystkich obecnych przeglądarkach i węźle 8
źródło
await/async
Przeglądarkę można podzielić na trzy części:
1) Pętla zdarzeń
2) Interfejs API sieci Web
3) Kolejka zdarzeń
Pętla zdarzeń działa wiecznie, tj. Jest to rodzaj nieskończonej pętli. Kolejka zdarzeń to miejsce, w którym wszystkie funkcje są wypychane w przypadku jakiegoś zdarzenia (na przykład: kliknięcie), to jest jedna po drugiej przeprowadzana z kolejki i umieszczana w pętli zdarzeń, która wykonuje tę funkcję i przygotowuje ją samodzielnie dla następnej po pierwszej. Oznacza to, że wykonanie jednej funkcji nie rozpoczyna się, dopóki funkcja przed kolejką nie zostanie wykonana w pętli zdarzeń.
Pomyślmy teraz, że wepchnęliśmy dwie funkcje w kolejce, jedna służy do pobierania danych z serwera, a inna wykorzystuje te dane. Najpierw umieściliśmy funkcję serverRequest () w kolejce, a następnie funkcję useData (). funkcja serverRequest przechodzi w pętlę zdarzeń i wywołuje serwer, ponieważ nigdy nie wiemy, ile czasu zajmie pobranie danych z serwera, więc oczekuje się, że ten proces zajmie trochę czasu, więc zajęliśmy się pętlą zdarzeń, zawieszając naszą stronę, tam gdzie jest sieć Interfejs API odgrywa rolę, bierze tę funkcję z pętli zdarzeń i zajmuje się zwolnieniem pętli zdarzeń z serwera, dzięki czemu możemy wykonać kolejną funkcję z kolejki. Następną funkcją w kolejce jest funkcja useiseData (), która przechodzi w pętlę, ale ze względu na brak dostępnych danych, przechodzi marnowanie i wykonywanie następnej funkcji trwa do końca kolejki. (Nazywa się to wywoływaniem asynchronicznym, tzn. możemy zrobić coś innego, dopóki nie otrzymamy danych)
Załóżmy, że nasza funkcja serverRequest () zawiera instrukcję return w kodzie, gdy otrzymamy dane z serwera Web API, wypchną je w kolejce na końcu kolejki. Gdy jest on wypychany na końcu w kolejce, nie możemy wykorzystać jego danych, ponieważ w naszej kolejce nie ma już funkcji do wykorzystania tych danych.Dlatego nie można zwrócić czegoś z Async Call.
Zatem rozwiązaniem tego jest oddzwonienie lub obietnica .
Obraz z jednej z odpowiedzi tutaj, poprawnie wyjaśnia użycie wywołania zwrotnego ... Dajemy naszą funkcję (funkcję wykorzystującą dane zwrócone z serwera) do funkcji wywołującej serwer.
W moim Kodeksie nazywa się to jako
Oddzwonienie Javscript.info
źródło
Możesz użyć tej niestandardowej biblioteki (napisanej przy użyciu Obietnicy), aby wykonać zdalne połączenie.
Prosty przykład użycia:
źródło
Innym rozwiązaniem jest wykonanie kodu za pomocą sekwencyjnego modułu wykonującego nsynjs .
Jeśli funkcja podstawowa jest obiecana
nsynjs oceni kolejno wszystkie obietnice i umieści wynik obietnicy we
data
właściwości:Jeśli funkcja podstawowa nie jest obiecana
Krok 1. Zawijaj funkcję z wywołaniem zwrotnym do opakowania obsługującego nsynjs (jeśli ma obiecaną wersję, możesz pominąć ten krok):
Krok 2. Włącz logikę synchroniczną w funkcję:
Krok 3. Uruchom funkcję w sposób synchroniczny przez nsynjs:
Nsynjs oceni krok po kroku wszystkie operatory i wyrażenia, wstrzymując wykonanie w przypadku, gdy wynik jakiejś wolnej funkcji nie będzie gotowy.
Więcej przykładów tutaj: https://github.com/amaksr/nsynjs/tree/master/examples
źródło
ECMAScript 6 ma „generatory”, które pozwalają łatwo programować w stylu asynchronicznym.
Aby uruchomić powyższy kod, wykonaj następujące czynności:
Jeśli potrzebujesz kierować na przeglądarki, które nie obsługują ES6, możesz uruchomić kod przez Babel lub kompilator zamykający, aby wygenerować ECMAScript 5.
Wywołanie zwrotne
...args
jest owinięte w tablicę i zniszczone podczas ich czytania, dzięki czemu wzorzec radzi sobie z wywołaniami zwrotnymi z wieloma argumentami. Na przykład z węzłem fs :źródło
Oto kilka podejść do pracy z żądaniami asynchronicznymi:
Przykład: jQuery odroczyła implementację do pracy z wieloma żądaniami
źródło
Znajdujemy się we wszechświecie, który wydaje się postępować wzdłuż wymiaru, który nazywamy „czasem”. Naprawdę nie rozumiemy, która jest godzina, ale opracowaliśmy abstrakcje i słownictwo, które pozwalają nam rozumować i mówić o tym: „przeszłość”, „teraźniejszość”, „przyszłość”, „przed”, „po”.
Systemy komputerowe, które budujemy - coraz więcej - mają czas jako ważny wymiar. Pewne rzeczy mają się wydarzyć w przyszłości. Potem inne rzeczy muszą się wydarzyć po tym, jak te pierwsze rzeczy w końcu się pojawią. Jest to podstawowe pojęcie zwane „asynchronicznością”. W naszym coraz bardziej połączonym w sieć świecie najczęstszym przypadkiem asynchroniczności jest oczekiwanie, aż jakiś zdalny system odpowie na jakieś żądanie.
Rozważ przykład. Dzwonisz do mleczarza i zamawiasz mleko. Jeśli chodzi, chcesz włożyć ją do kawy. Nie możesz teraz wlewać mleka do kawy, ponieważ jeszcze jej tu nie ma. Musisz poczekać, aż nadejdzie, zanim włożysz go do kawy. Innymi słowy, następujące funkcje nie będą działać:
Ponieważ JS nie ma sposobu, aby wiedzieć, że musi czekać na
order_milk
zakończenie przed wykonaniemput_in_coffee
. Innymi słowy, nie wie, żeorder_milk
jest asynchroniczny - to coś, co nie spowoduje mleka aż do pewnego czasu. JS i inne języki deklaratywne wykonują jedną instrukcję po drugiej bez czekania.Klasyczne podejście JS do tego problemu, wykorzystujące fakt, że JS obsługuje funkcje jako obiekty pierwszej klasy, które można przekazywać, polega na przekazaniu funkcji jako parametru do żądania asynchronicznego, które następnie wywoła po zakończeniu. jego zadanie kiedyś w przyszłości. To jest podejście „oddzwaniania”. To wygląda tak:
order_milk
rozpoczyna, zamawia mleko, a potem, kiedy tylko dotrze, przywołujeput_in_coffee
.Problem z tym podejściem zwrotnym polega na tym, że zanieczyszcza on normalną semantykę funkcji zgłaszającej jej wynik
return
; zamiast tego funkcje nie mogą raportować swoich wyników przez wywołanie wywołania zwrotnego podanego jako parametr. Również to podejście może szybko stać się niewygodne w przypadku dłuższych sekwencji zdarzeń. Powiedzmy na przykład, że chcę poczekać, aż mleko zostanie wlane do kawy, i wtedy i tylko wtedy wykonaj trzeci krok, a mianowicie wypicie kawy. W końcu muszę napisać coś takiego:gdzie przechodzę
put_in_coffee
zarówno do mleka, aby je wlać, jak i do akcji (drink_coffee
), które należy wykonać po włożeniu mleka. Taki kod staje się trudny do napisania, odczytania i debugowania.W takim przypadku możemy przepisać kod w pytaniu jako:
Wprowadź obietnice
To była motywacja do pojęcia „obietnicy”, która jest szczególnym rodzajem wartości, która reprezentuje jakiś przyszły lub asynchroniczny rezultat. Może reprezentować coś, co już się wydarzyło lub wydarzy się w przyszłości, lub może nigdy się nie wydarzyć. Obietnice mają pojedynczą, nazwaną metodę,
then
której przekazujesz akcję, która ma zostać wykonana, gdy wynik reprezentacji obietnicy zostanie zrealizowany.W przypadku naszego mleka i kawy, możemy zaprojektować
order_milk
, aby powrócić obietnicę dla mleka przybyciu, a następnie określićput_in_coffee
jakothen
działania, co następuje:Zaletą tego jest to, że możemy połączyć je ze sobą, aby utworzyć sekwencje przyszłych zdarzeń („łączenie”):
Zastosujmy obietnice do twojego konkretnego problemu. Opakowamy naszą logikę żądania do funkcji, która zwraca obietnicę:
W rzeczywistości wszystko, co zrobiliśmy, zostało dodane
return
do wezwania do$.ajax
. Działa to, ponieważ jQuery$.ajax
zwraca już coś w rodzaju obietnicy. (W praktyce, bez wchodzenia w szczegóły, wolelibyśmy zawinąć to wezwanie, aby zwrócić prawdziwą obietnicę, lub użyć innej alternatywy do$.ajax
tego.) Teraz, jeśli chcemy załadować plik i poczekać, aż zakończy się i następnie zrób coś, możemy po prostu powiedziećna przykład,
Podczas korzystania z obietnic kończymy przekazywanie wielu funkcji
then
, więc często pomocne jest użycie bardziej kompaktowych funkcji strzałek w stylu ES6:Słowo
async
kluczoweAle wciąż jest coś niejasno niezadowalającego w konieczności pisania kodu w jedną stronę, jeśli jest synchroniczna, i całkiem inną, jeśli w sposób asynchroniczny. Dla synchronicznych piszemy
ale jeśli
a
jest asynchroniczny, z obietnicami musimy napisaćPowyżej powiedzieliśmy: „JS nie ma sposobu, aby wiedzieć, że musi poczekać na zakończenie pierwszego połączenia, zanim wykona drugie”. Czy nie byłoby miło, gdyby tam był jakiś sposób, aby powiedzieć, że JS? Okazuje się, że istnieje -
await
słowo kluczowe używane w specjalnym typie funkcji zwanej funkcją „asynchroniczną”. Ta funkcja jest częścią nadchodzącej wersji ES, ale jest już dostępna w transpilerach, takich jak Babel, przy odpowiednich ustawieniach wstępnych. To pozwala nam po prostu pisaćW twoim przypadku byłbyś w stanie napisać coś takiego
źródło
Krótka odpowiedź : Twoja
foo()
metoda zwraca natychmiast, a$ajax()
wywołanie jest wykonywane asynchronicznie po powrocie funkcji . Problem polega na tym, jak i gdzie przechowywać wyniki pobrane przez wywołanie asynchroniczne po jego zwróceniu.W tym wątku podano kilka rozwiązań. Być może najłatwiejszym sposobem jest przekazanie obiektu do
foo()
metody i zapisanie wyników w elemencie tego obiektu po zakończeniu wywołania asynchronicznego.Pamiętaj, że połączenie do
foo()
nadal nie zwróci niczego przydatnego. Jednak wynik wywołania asynchronicznego zostanie teraz zapisany wresult.response
.źródło
Użyj
callback()
funkcji wewnątrzfoo()
sukcesu. Spróbuj w ten sposób. Jest prosty i łatwy do zrozumienia.źródło
Pytanie brzmiało:
które MOŻNA interpretować jako:
Rozwiązaniem będzie uniknięcie wywołań zwrotnych i użycie kombinacji Obietnic i asynchronizacji / oczekiwania .
Chciałbym podać przykład żądania Ajax.
(Chociaż można go napisać w Javascripcie, wolę napisać go w Pythonie i skompilować do Javascript za pomocą Transcrypt . Będzie wystarczająco jasny).
Pozwala najpierw włączyć użycie JQuery, aby było
$
dostępne jakoS
:Zdefiniuj funkcję, która zwraca Obietnicę , w tym przypadku wywołanie Ajax:
Użyj kodu asynchronicznego tak, jakby był synchroniczny :
źródło
Korzystanie z obietnicy
Najdoskonalszą odpowiedzią na to pytanie jest użycie
Promise
.Stosowanie
Ale poczekaj...!
Wystąpił problem z korzystaniem z obietnic!
Dlaczego powinniśmy korzystać z naszej niestandardowej obietnicy?
Używałem tego rozwiązania przez pewien czas, dopóki nie zorientowałem się, że w starych przeglądarkach występuje błąd:
Więc postanowiłem zaimplementować własną klasę Promise dla ES3 do kompilatorów poniżej js, jeśli nie jest zdefiniowana. Po prostu dodaj ten kod przed głównym kodem, a następnie bezpiecznie skorzystaj z Promise!
źródło
Oczywiście istnieje wiele podejść, takich jak żądanie synchroniczne, obiecuję, ale z mojego doświadczenia uważam, że powinieneś zastosować podejście zwrotne. Naturalne jest zachowanie asynchroniczne Javascript. Tak więc fragment kodu można przepisać nieco inaczej:
źródło
Zamiast rzucać w ciebie kodem, istnieją 2 koncepcje, które są kluczowe dla zrozumienia, w jaki sposób JS obsługuje wywołania zwrotne i asynchroniczność. (czy to w ogóle słowo?)
Model pętli zdarzeń i współbieżności
Są trzy rzeczy, o których musisz wiedzieć; Kolejka; pętla zdarzeń i stos
Mówiąc w uproszczeniu, pętla zdarzeń przypomina kierownika projektu, stale nasłuchuje wszelkich funkcji, które chcą uruchomić, i komunikuje się między kolejką a stosem.
Gdy otrzyma komunikat o uruchomieniu czegoś, dodaje go do kolejki. Kolejka to lista rzeczy, które czekają na wykonanie (np. Żądanie AJAX). wyobraź sobie to tak:
Kiedy jeden z tych komunikatów ma zostać wykonany, wyskakuje z kolejki i tworzy stos, stos to wszystko, co JS musi wykonać, aby wykonać instrukcję zawartą w komunikacie. W naszym przykładzie mówi się, żeby zadzwonić
foobarFunc
Więc wszystko, co foobarFunc musi wykonać (w naszym przypadku
anotherFunction
) zostanie zepchnięte na stos. wykonane, a następnie zapomniane o - pętla zdarzeń przejdzie następnie do kolejnej rzeczy w kolejce (lub będzie nasłuchiwać wiadomości)Kluczową kwestią jest tutaj kolejność wykonywania. To jest
KIEDY coś się uruchomi
Gdy wykonujesz połączenie za pomocą AJAX do strony zewnętrznej lub uruchamiasz jakikolwiek kod asynchroniczny (na przykład setTimeout), JavaScript jest zależny od odpowiedzi, zanim będzie mógł kontynuować.
Najważniejsze pytanie brzmi: kiedy otrzyma odpowiedź? Odpowiedź brzmi: nie wiemy - więc pętla zdarzeń czeka na komunikat z komunikatem „hej, uruchom mnie”. Jeśli JS po prostu zaczeka synchronicznie na tę wiadomość, aplikacja zawiesi się i będzie do dupy. Tak więc JS kontynuuje wykonywanie następnego elementu w kolejce, czekając, aż wiadomość zostanie dodana z powrotem do kolejki.
Dlatego w funkcji asynchronicznej używamy funkcji nazywanych wywołaniami zwrotnymi . To trochę jak obietnica dosłownie. Tak jak w obietnicy zwrócenia czegoś w pewnym momencie, jQuery używa określonych wywołań zwrotnych o nazwie
deffered.done
deffered.fail
ideffered.always
(między innymi). Możesz je wszystkie zobaczyć tutajTak więc musisz przekazać funkcję, która ma zostać wykonana w pewnym momencie z przekazanymi do niej danymi.
Ponieważ wywołanie zwrotne nie jest wykonywane natychmiast, ale później ważne jest, aby przekazać odwołanie do funkcji, która nie została wykonana. więc
więc przez większość czasu (ale nie zawsze) będziesz przechodzić
foo
niefoo()
Mam nadzieję, że to będzie miało sens. Kiedy napotykasz takie rzeczy, które wydają się mylące - bardzo polecam przeczytanie dokumentacji w pełni, aby przynajmniej ją zrozumieć. Dzięki temu będziesz znacznie lepszym programistą.
źródło
Używając ES2017, powinieneś mieć to jako deklarację funkcji
I wykonanie tego w ten sposób.
Lub składnia Promise
źródło