JavaScript: Jak Efektywnie Czekać na Jeden z Wielu Promisów?
Odkryj Promise.race() i Promise.any(), aby zoptymalizować asynchroniczne operacje i reagować na najszybszy wynik lub pierwszy sukces.
Najważniejsze Informacje
Promise.race(): Idealne, gdy chcesz zareagować na pierwszy promis, który się zakończy (spełni lub odrzuci). Zwraca jego wynik lub błąd.Promise.any(): Użyteczne, gdy interesuje Cię pierwszy spełniony promis, a odrzucenia mają być ignorowane, chyba że wszystkie promisy zawiodą.- Obsługa błędów jest kluczowa: Zawsze implementuj mechanizmy obsługi błędów, używając
.catch()lub blokówtry/catchw funkcjachasync/await, aby Twoje aplikacje były stabilne.
Wprowadzenie do Promisów w JavaScript
Zanim zagłębimy się w szczegóły oczekiwania na jeden z wielu promisów, warto krótko przypomnieć, czym są promisy. W JavaScript, Promis (Promise) to obiekt reprezentujący ostateczny wynik (lub niepowodzenie) operacji asynchronicznej. Operacja asynchroniczna to taka, która nie blokuje głównego wątku wykonania programu, pozwalając aplikacji pozostać responsywną (np. pobieranie danych z serwera, odczyt pliku).
Promis może znajdować się w jednym z trzech stanów:
- Oczekujący (pending): Stan początkowy; operacja asynchroniczna jeszcze się nie zakończyła.
- Spełniony (fulfilled/resolved): Operacja asynchroniczna zakończyła się sukcesem, a promis przechowuje jej wynik.
- Odrzucony (rejected): Operacja asynchroniczna zakończyła się błędem, a promis przechowuje powód tego błędu.
Wizualizacja trzech stanów obiektu Promise: Oczekujący, Spełniony i Odrzucony.
Kiedy pracujesz z wieloma operacjami asynchronicznymi jednocześnie, często pojawia się potrzeba zarządzania ich wynikami w specyficzny sposób – na przykład, czekając tylko na najszybszą z nich.
Główne Rozwiązanie: Promise.race()
Jeśli chcesz, aby Twoja funkcja w JavaScript poczekała na wypełnienie się (czyli rozstrzygnięcie – spełnienie lub odrzucenie) jednego z dwóch (lub więcej) promisów, standardowym i najczęściej używanym rozwiązaniem jest metoda Promise.race().
Jak Działa Promise.race()?
Metoda Promise.race() przyjmuje jako argument iterowalny obiekt (najczęściej tablicę) promisów. Zwraca ona nowy promis, który rozstrzyga się (spełnia lub odrzuca) tak szybko, jak tylko pierwszy z promisów w przekazanej tablicy się rozstrzygnie. Innymi słowy, "wygrywa" ten promis, który jako pierwszy zakończy swoje działanie, a wynik (lub błąd) tego promisa staje się wynikiem (lub błędem) promisa zwróconego przez Promise.race().
Ilustracja pokazująca, jak Promise.race() zwraca wynik pierwszego promisa, który się zakończy.
Przykład Użycia Promise.race():
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('Wynik z Promisa 1 (szybszy)'), 500);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => resolve('Wynik z Promisa 2 (wolniejszy)'), 1000);
});
const promise3WithError = new Promise((resolve, reject) => {
setTimeout(() => reject('Błąd z Promisa 3 (najszybszy, ale odrzucony)'), 200);
});
// Scenariusz 1: Wyścig między promise1 i promise2
Promise.race([promise1, promise2])
.then(wynik => {
console.log('Pierwszy spełniony promis (scenariusz 1):', wynik); // Wyświetli: "Wynik z Promisa 1 (szybszy)"
})
.catch(blad => {
console.error('Błąd (scenariusz 1):', blad);
});
// Scenariusz 2: Wyścig, gdzie najszybszy promis jest odrzucony
Promise.race([promise1, promise2, promise3WithError])
.then(wynik => {
console.log('Pierwszy spełniony promis (scenariusz 2):', wynik);
})
.catch(blad => {
console.error('Pierwszy odrzucony promis (scenariusz 2):', blad); // Wyświetli: "Błąd z Promisa 3 (najszybszy, ale odrzucony)"
});
W pierwszym scenariuszu promise1 jest szybszy, więc Promise.race() przyjmie jego wartość. W drugim scenariuszu promise3WithError jest najszybszy, ale zostaje odrzucony, więc Promise.race() również zostanie odrzucony z jego błędem.
Zachowanie w Przypadku Odrzucenia
Jak widać w powyższym przykładzie, jeśli pierwszy promis, który się rozstrzygnie, jest odrzucony, to promis zwrócony przez Promise.race() również zostanie odrzucony z tym samym powodem błędu. To ważne do zapamiętania podczas implementacji obsługi błędów.
Alternatywa: Promise.any() dla Pierwszego Sukcesu
Co jeśli zależy Ci tylko na pierwszym spełnionym promisie i chcesz zignorować te, które zostały odrzucone (chyba że wszystkie zawiodą)? W takim przypadku lepszym wyborem może być metoda Promise.any().
Jak Działa Promise.any()?
Promise.any() również przyjmuje tablicę promisów, ale zwraca promis, który spełnia się wartością pierwszego promisa z tablicy, który został spełniony. Jeśli wszystkie promisy w tablicy zostaną odrzucone, Promise.any() zwraca promis odrzucony z obiektem AggregateError, który zawiera tablicę powodów odrzucenia wszystkich promisów.
Przykład Użycia Promise.any():
const promiseWithErrorFirst = new Promise((resolve, reject) => {
setTimeout(() => reject('Błąd - pierwszy promis'), 100);
});
const promiseWithSuccessSecond = new Promise((resolve, reject) => {
setTimeout(() => resolve('Sukces - drugi promis!'), 500);
});
const promiseWithSuccessThird = new Promise((resolve, reject) => {
setTimeout(() => resolve('Sukces - trzeci promis (wolniejszy)'), 1000);
});
// Scenariusz 1: Pierwszy promis odrzucony, drugi spełniony
Promise.any([promiseWithErrorFirst, promiseWithSuccessSecond, promiseWithSuccessThird])
.then(wynik => {
console.log('Pierwszy spełniony promis (any - scenariusz 1):', wynik); // Wyświetli: "Sukces - drugi promis!"
})
.catch(agregowanyBlad => {
console.error('Wszystkie promisy odrzucone (any - scenariusz 1):', agregowanyBlad.errors);
});
// Scenariusz 2: Wszystkie promisy odrzucone
const pErr1 = new Promise((resolve, reject) => setTimeout(() => reject('Błąd 1'), 100));
const pErr2 = new Promise((resolve, reject) => setTimeout(() => reject('Błąd 2'), 200));
Promise.any([pErr1, pErr2])
.then(wynik => {
console.log('Pierwszy spełniony promis (any - scenariusz 2):', wynik);
})
.catch(agregowanyBlad => {
console.error('Wszystkie promisy odrzucone (any - scenariusz 2):', agregowanyBlad.errors); // Wyświetli tablicę błędów: ['Błąd 1', 'Błąd 2']
});
Promise.any() jest szczególnie przydatne, gdy masz kilka alternatywnych źródeł danych i chcesz użyć pierwszego, które dostarczy poprawny wynik.
Porównanie Metod Współbieżności Promisów
Aby lepiej zrozumieć, kiedy używać której metody, poniższa tabela przedstawia kluczowe różnice między Promise.race(), Promise.any(), a także powiązanymi metodami Promise.all() i Promise.allSettled().
| Cecha | Promise.race([p1, p2, ...]) |
Promise.any([p1, p2, ...]) |
Promise.all([p1, p2, ...]) |
Promise.allSettled([p1, p2, ...]) |
|---|---|---|---|---|
| Czeka na | Pierwszy promis, który się rozstrzygnie (spełni lub odrzuci) | Pierwszy promis, który się spełni | Wszystkie promisy, które się spełnią | Wszystkie promisy, które się rozstrzygną (spełnią lub odrzucą) |
| Rozstrzyga się gdy | Jeden z promisów się rozstrzygnie | Jeden z promisów się spełni LUB wszystkie zostaną odrzucone | Wszystkie promisy się spełnią LUB jeden zostanie odrzucony | Wszystkie promisy się rozstrzygną |
| Wartość sukcesu | Wartość pierwszego rozstrzygniętego promisa (jeśli spełniony) | Wartość pierwszego spełnionego promisa | Tablica wartości wszystkich spełnionych promisów (w kolejności) | Tablica obiektów opisujących wynik każdego promisa ({status: 'fulfilled', value: ...}) |
| Wartość błędu | Powód odrzucenia pierwszego rozstrzygniętego promisa (jeśli odrzucony) | AggregateError zawierający błędy wszystkich promisów (jeśli wszystkie odrzucone) |
Powód odrzucenia pierwszego odrzuconego promisa | Tablica obiektów opisujących wynik każdego promisa ({status: 'rejected', reason: ...}) |
| Główny cel | Uzyskanie najszybszego wyniku, niezależnie od sukcesu/błędu | Uzyskanie pierwszego sukcesu, ignorowanie początkowych błędów | Agregacja wyników wielu operacji, które wszystkie muszą się powieść | Zebranie informacji o wszystkich wynikach, niezależnie od ich statusu |
Najlepsze Praktyki i Wskazówki
Używaj Składni async/await dla Lepszej Czytelności
Składnia async/await sprawia, że kod asynchroniczny wygląda i zachowuje się bardziej jak kod synchroniczny, co ułatwia jego zrozumienie i zarządzanie.
async function czekajNaPierwszy(promise1, promise2) {
try {
const pierwszyWynik = await Promise.race([promise1, promise2]);
console.log('Pierwszy wynik (async/await):', pierwszyWynik);
// Dalsze operacje na wyniku
} catch (blad) {
console.error('Błąd w `czekajNaPierwszy` (async/await):', blad);
// Obsługa błędu
}
}
Zawsze Pamiętaj o Obsłudze Błędów
Niezależnie od tego, czy używasz .then().catch(), czy async/await z blokami try...catch, obsługa potencjalnych błędów jest niezbędna do tworzenia stabilnych aplikacji. Nieułowione odrzucone promisy mogą prowadzić do nieoczekiwanych zachowań lub awarii.
Praktyczne Zastosowania
- Implementacja Timeoutów:
Promise.race()jest idealne do ustawiania limitu czasu dla operacji asynchronicznej. Możesz stworzyć promis, który odrzuca się po określonym czasie, i "ścigać" go z promisem reprezentującym Twoją główną operację. - Konkurencyjne Pobieranie Zasobów: Jeśli ten sam zasób jest dostępny z wielu źródeł (np. różnych serwerów CDN), możesz użyć
Promise.race()(lubPromise.any(), jeśli interesuje Cię tylko sukces) do pobrania go z najszybszego źródła. - Wybór Najszybszej Odpowiedzi API: Jeśli masz kilka redundantnych endpointów API,
Promise.race()pozwoli Ci przetworzyć odpowiedź z tego, który zareaguje najszybciej.
Wizualizacja Charakterystyk Metod Obsługi Promisów
Poniższy wykres radarowy przedstawia porównanie kluczowych charakterystyk różnych metod obsługi promisów w JavaScript. Skala ocen od 1 (najniższa) do 10 (najwyższa) odzwierciedla, jak dana metoda wypada w kontekście określonej cechy. Wykres ten pomoże Ci wizualnie zrozumieć, która metoda najlepiej pasuje do Twoich potrzeb w zależności od priorytetów projektu.
Jak widać, Promise.race() wyróżnia się najwyższą szybkością reakcji na pierwszy wynik (niezależnie czy to sukces, czy błąd) oraz prostotą użycia. Z kolei Promise.any() jest najlepsze, gdy priorytetem jest zignorowanie błędów i uzyskanie pierwszego sukcesu. Metody Promise.all() i Promise.allSettled() oferują większą kompletność wyników kosztem szybkości reakcji na pojedyncze zdarzenie.
Mapa Myśli: Strategie Oczekiwania na Promisy
Poniższa mapa myśli wizualizuje główne strategie oczekiwania na promisy w JavaScript, skupiając się na Promise.race() i Promise.any(). Przedstawia ich kluczowe charakterystyki i typowe scenariusze użycia, co może pomóc w wyborze odpowiedniej metody dla konkretnego problemu.
Czeka na PIERWSZY
rozstrzygnięty promis
(spełniony LUB odrzucony)"] id1_2["Wynik:
Wartość/błąd
pierwszego promisa"] id1_3["Zastosowania:"] id1_3_1["Implementacja timeoutów
dla operacji asynchronicznych"] id1_3_2["Wybór najszybszego
źródła danych/odpowiedzi API"] id1_3_3["Reakcja na pierwsze
zdarzenie z wielu
monitorowanych operacji"] id2["Promise.any()"] id2_1["Charakterystyka:
Czeka na PIERWSZY
SPEŁNIONY promis"] id2_2["Wynik:
Wartość pierwszego
spełnionego promisa"] id2_3["Obsługa błędów:
Ignoruje odrzucone promisy,
chyba że WSZYSTKIE
zostaną odrzucone
(wtedy zwraca AggregateError)"] id2_4["Zastosowania:"] id2_4_1["Pobieranie zasobu
z wielu lokalizacji,
akceptując pierwszy dostępny
i poprawny wynik"] id2_4_2["Funkcje z opcjonalnymi
szybkimi ścieżkami lub
zasobami zapasowymi"]
Mapa myśli podkreśla, że wybór między Promise.race() a Promise.any() zależy od tego, czy ważniejsza jest reakcja na jakiekolwiek pierwsze zdarzenie (nawet błąd), czy też oczekiwanie na pierwszy pozytywny wynik.
Zobacz w Akcji: Promise.race() Wyjaśnione
Aby lepiej zrozumieć działanie Promise.race() w praktyce, poniższy materiał wideo dostarcza zwięzłego wyjaśnienia tej metody wraz z przykładami kodu. Obejrzenie go może pomóc w utrwaleniu wiedzy na temat tego, jak JavaScript radzi sobie z "wyścigiem" promisów.
Film ten (#25 - Promise.race() in JavaScript) pokazuje podstawowe użycie Promise.race() i ilustruje, jak reaguje on na promisy, które rozwiązują się w różnym czasie, w tym zarówno na sukcesy, jak i na błędy.
FAQ: Najczęściej Zadawane Pytania
Czym dokładnie jest Promise.race()?
Promise.race() to metoda w JavaScript, która przyjmuje tablicę (lub inny iterowalny obiekt) promisów i zwraca nowy promis. Ten nowy promis rozstrzyga się (czyli spełnia się lub odrzuca) tak szybko, jak tylko pierwszy z promisów w przekazanej tablicy się rozstrzygnie. Wynik lub błąd tego pierwszego promisa staje się wynikiem lub błędem promisa zwróconego przez Promise.race().
Kiedy powinienem użyć Promise.any() zamiast Promise.race()?
Użyj Promise.any(), gdy interesuje Cię tylko pierwszy sukces (spełniony promis) spośród wielu operacji asynchronicznych i chcesz ignorować te promisy, które zostały odrzucone – chyba że wszystkie promisy w tablicy zostaną odrzucone. W przeciwieństwie do tego, Promise.race() zareaguje na pierwsze jakiekolwiek rozstrzygnięcie, niezależnie od tego, czy jest to sukces, czy błąd.
Co się stanie, jeśli pierwszy promis w Promise.race() zostanie odrzucony?
Jeśli pierwszy promis (czyli ten, który najszybciej zakończy swoje działanie) w tablicy przekazanej do Promise.race() zostanie odrzucony, to cały promis zwrócony przez Promise.race() również zostanie natychmiast odrzucony, z tym samym powodem błędu.
Jak obsłużyć błędy przy użyciu tych metod?
Błędy z promisów zwróconych przez Promise.race() lub Promise.any() można obsłużyć na dwa główne sposoby:
- Dołączając metodę
.catch(error => { /* obsługa błędu */ })do łańcucha promisów. - Używając bloku
try...catch, jeśli pracujesz ze składniąasync/await:try { await Promise.race(...); } catch (error) { /* obsługa błędu */ }.
W przypadku Promise.any(), jeśli wszystkie promisy zostaną odrzucone, blok .catch() (lub catch w try...catch) otrzyma obiekt AggregateError, który zawiera tablicę błędów ze wszystkich odrzuconych promisów.
Czy mogę używać Promise.race() do implementacji timeoutu?
Tak, Promise.race() jest bardzo popularnym i skutecznym sposobem na implementację mechanizmu timeout dla operacji asynchronicznych. Można to zrobić, tworząc jeden promis reprezentujący Twoją główną, długotrwałą operację, a drugi promis, który automatycznie odrzuca się po określonym czasie (np. używając setTimeout). Następnie przekazujesz oba promisy do Promise.race(). Jeśli główna operacja zakończy się przed upływem limitu czasu, jej wynik zostanie zwrócony. Jeśli timeout upłynie pierwszy, promis od timeoutu zostanie odrzucony, co pozwoli Ci odpowiednio zareagować na przekroczenie limitu czasu.
Rekomendowane Zapytania
Aby pogłębić swoją wiedzę na temat zarządzania operacjami asynchronicznymi w JavaScript, rozważ następujące tematy:
-
Jak zaimplementować timeout dla operacji asynchronicznej w JavaScript przy użyciu
Promise.race()? -
Czym różni się
Promise.all()odPromise.allSettled()w JavaScript? - Jak skutecznie zarządzać wieloma równoległymi zapytaniami API w JavaScript?
-
Praktyczne przykłady użycia
async/awaitz metodamiPromise.race()iPromise.any().
Referencje
Poniżej znajdują się linki do dokumentacji i artykułów, które mogą być pomocne w dalszym zgłębianiu tematu promisów w JavaScript:
Last updated May 14, 2025