Promise, async/await i fetch w JavaScript: 15 przykładów do uruchomienia | We.Inc
Promise, async/await i fetch API z osią czasu event loopa krok po kroku i 15 przykładami, które możesz edytować i uruchamiać: JSON, formularze, błędy, żądania równoległe, AbortController i ponawianie.
Promise, async/await i fetch w JavaScript
Promise reprezentuje wynik, który nadejdzie później. Funkcje async zwracają promise, a await wstrzymuje taką funkcję, aż promise się rozstrzygnie. fetch(url) zwraca promise obiektu Response: sprawdź response.ok, a potem użyj await response.json(). Otaczaj await blokiem try i catch, używaj Promise.all do żądań równoległych i AbortController do anulowania.
Wzorzec, którego potrzebuje większość stron
Krok
Kod
Żądanie
const res = await fetch(url)
Sprawdzenie statusu
if (!res.ok) throw new Error(res.status)
Odczyt treści
const data = await res.json()
Obsługa błędu
try { ... } catch (err) { ... }
Zacznij od osi czasu, aby zobaczyć, dlaczego kod po await może wykonać się przed nim. Potem edytuj dowolny przykład, a uruchomi się od razu ponownie. Żądania używają tu adresów data: lub udawanych serwerów, więc nic nie opuszcza Twojej przeglądarki.
Przejdź krok po kroku przez oś czasu, aby zobaczyć stos wywołań i dwie kolejki.
Uruchom przykład 1 i przewidź kolejność, zanim spojrzysz.
Przerób fetch, błędy i sprawdzanie statusu w przykładach od 5 do 8.
Porównaj czasy wykonania sekwencyjnego i równoległego w przykładzie 9.
Skopiuj pomocnicze funkcje do anulowania, limitu czasu i ponawiania do własnego kodu.
Nigdy nie zostawiaj promise bez catch gdzieś wyżej w łańcuchu; nieobsłużone odrzucenia ukrywają prawdziwe błędy.
Uruchom niezależne żądania, zanim zaczniesz czekać na którekolwiek, a potem użyj await Promise.all.
Sprawdzaj res.ok przy każdym fetch. Strona 404 parsowana jako JSON rzuca mylący błąd składni.
Unikaj await wewnątrz forEach; ono nie czeka. Użyj for...of dla sekwencji albo map z Promise.all dla równoległości.
Przerywaj żądania w funkcjach czyszczących efektów React, aby wolna odpowiedź nie mogła zaktualizować komponentu, którego już nie ma.
Najczęściej zadawane pytania
Czym różnią się promise od async/await?
Niczym, jeśli chodzi o możliwości: async/await jest zbudowane na promise. Funkcja async zawsze zwraca promise, a await go rozpakowuje. Czyta się to jak zwykły kod krok po kroku, co ułatwia obsługę błędów przez try i catch.
Dlaczego fetch nie rzuca błędu przy 404?
fetch traktuje każdą odpowiedź HTTP jako udane żądanie, bo serwer odpowiedział. Odrzuca tylko przy awariach sieci lub anulowanym żądaniu. Sprawdzaj response.ok lub response.status samodzielnie.
Dlaczego setTimeout z 0 wykonuje się po rozwiązanym promise?
Callbacki promise trafiają do kolejki mikrozadań, która jest opróżniana w całości po bieżącym skrypcie. Callback timera to zadanie, które czeka na następny obrót event loopa.
Jak anulować żądanie fetch?
Utwórz AbortController, przekaż controller.signal w opcjach fetch i wywołaj controller.abort(). Promise z fetch zostanie odrzucony z błędem o nazwie AbortError.
Czy mogę używać await na najwyższym poziomie?
Tak, wewnątrz modułów JavaScript (script type="module" lub dowolny zbundlowany moduł ES). W klasycznych skryptach owiń kod w funkcję async.
Błędy, które zobaczysz
"Unexpected token < in JSON" oznacza, że serwer wysłał HTML, zwykle stronę błędu, a .json() próbowało go sparsować. Najpierw sprawdź res.ok i Content-Type.
"Failed to fetch" lub TypeError to awaria sieci lub CORS. Przeglądarka celowo ukrywa szczegóły; otwórz kartę Network, aby zobaczyć, czy żądanie zostało zablokowane.
Żądaniu, które działa w narzędziu, ale nie w przeglądarce, często brakuje nagłówków CORS na serwerze. To trzeba naprawić na serwerze, a nie w fetch.
Zastosowanie na prawdziwej stronie
Formularz kontaktowy wysyła swoje FormData do endpointu formularza, pokazuje stan wysyłania i podmienia treść na podziękowanie dopiero wtedy, gdy res.ok jest true. Zachowaj zwykły formularz HTML działający bez JavaScriptu jako rozwiązanie zapasowe.
Na stronach, które wczytują dane przy ładowaniu, pokazuj szkielet lub krótki komunikat podczas oczekiwania, a w razie niepowodzenia błąd z przyciskiem ponowienia, zamiast pustego pola.
Promise i async/await
1. Co wykonuje się kiedy
Najpierw wykonują się linie synchroniczne, potem każdy zakolejkowany callback promise (mikrozadania), a na końcu timer (zadanie). Zmień kolejność i uruchom ponownie.
2. Tworzenie promise
Promise rozstrzyga się raz: resolve daje wartość, reject daje błąd. then i catch wykonują się później, nigdy od razu.
3. To samo z async i await
await wstrzymuje tylko funkcję async, w której się znajduje. Linia po wywołaniu main() wykonuje się, zanim main się zakończy.
4. Błędy z try, catch i finally
Wewnątrz funkcji async odrzucony await rzuca błąd, więc działa zwykłe try i catch. finally to miejsce na ukrycie spinnera.
fetch w prawdziwych zadaniach na stronie
5. Wczytaj JSON przez fetch
fetch zwraca Response; .json() odczytuje i parsuje treść, co jest drugim await. Ten przykład używa adresu data:, więc działa offline.
6. Dla fetch 404 to nie błąd
fetch odrzuca tylko wtedy, gdy żądania w ogóle nie da się wykonać (offline, blokada, zły URL). 404 lub 500 rozwiązuje się normalnie z ok ustawionym na false, więc zawsze to sprawdzaj.
7. Wyślij formularz przez fetch
FormData odczytuje każde nazwane pole. Tutaj żądanie jest budowane, ale nie wysyłane, a jego treść jest wypisywana, abyś zobaczył dokładnie, co dostałby serwer.
8. POST z JSON
Wyślij JSON, zamieniając go na tekst i samodzielnie ustawiając nagłówek Content-Type; fetch nie robi żadnej z tych rzeczy dla zwykłych obiektów.
Kilka żądań naraz
Której funkcji pomocniczej promise użyć
Funkcja
Rozstrzyga się, gdy
Użyj do
Promise.all
Wszystkie się spełnią albo pierwszy zostanie odrzucony
Danych, których potrzebujesz w całości
Promise.allSettled
Każdy promise się rozstrzygnął
Niezależnych widżetów; raportuj każdy wynik
Promise.race
Pierwszy się rozstrzygnie, w dowolny sposób
Wyścigu timera z żądaniem
Promise.any
Pierwszy się spełni (odrzuca tylko, gdy wszystkie zostaną odrzucone)
Próbowania serwerów lustrzanych; bierzesz najszybszy sukces
9. Sekwencyjnie kontra równolegle
Czekanie na jedno wywołanie naraz sumuje czasy oczekiwania. Uruchomienie wszystkich i await Promise.all czeka tylko tyle, ile najwolniejsze.
10. Promise.all kontra Promise.allSettled
Promise.all odrzuca, gdy tylko jeden zawiedzie. allSettled czeka na wszystkie i mówi, które zadziałały, co pasuje do dashboardów z niezależnymi widżetami.
11. Wiele żądań, po kilka naraz
Wysłanie setek żądań naraz może przekroczyć limity. Ta mała pula utrzymuje w toku najwyżej trzy.
Anulowanie, limit czasu, ponawianie
12. Anulowanie przez AbortController
Przekaż controller.signal do fetch i wywołaj abort(), aby je zatrzymać. Promise zostaje odrzucony z AbortError, który zwykle się ignoruje.
13. Rezygnacja po limicie czasu
AbortSignal.timeout(ms) tworzy sygnał, który przerywa sam. Przy fetch błąd nazywa się TimeoutError, więc można go odróżnić od anulowania przez użytkownika.
14. Ponawianie z rosnącym opóźnieniem
Ponawiaj tylko błędy, które mogą minąć, takie jak przekroczenie czasu lub 503, i za każdym razem czekaj dłużej, aby przeciążony serwer mógł dojść do siebie.
15. Wyszukiwanie podczas pisania bez nieaktualnych wyników
Poczekaj na przerwę w pisaniu i przerwij poprzednie żądanie, gdy zaczyna się nowe, aby stara, wolna odpowiedź nigdy nie nadpisała nowszej.
Masz odpowiedź, którą musisz odczytać? Wklej ją do narzędzia
formatowanie JSON
Cały skrypt to jedno zadanie. Zaczyna się na stosie wywołań.
Linia 1 wykonuje się od razu i wypisuje A.
setTimeout przekazuje callback timerowi. Po 0 ms czeka on w kolejce zadań.
Promise jest już rozwiązany, więc jego callback trafia prosto do kolejki mikrozadań.
Linia 4 jest nadal synchroniczna, więc D wypisuje się przed czymkolwiek z kolejek.
Skrypt się kończy i stos jest pusty. Teraz każde mikrozadanie wykonuje się przed jakimkolwiek zadaniem.
Callback promise wykonuje się i wypisuje C.
Mikrozadania są puste, więc event loop bierze następne zadanie: timer wypisuje B.