Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Promise.all, Promise.race, Promise.allSettled

W poprzednich ćwiczeniach skupialiśmy się na pojedynczych Promise oraz ich łańcuchach. Jednak w rzeczywistych aplikacjach, takich jak system zarządzania Parkiem Jurajskim, często musimy pracować z wieloma operacjami asynchronicznymi jednocześnie. JavaScript oferuje specjalne metody pomocnicze na obiekcie Promise, które pozwalają na elegancką kompozycję i koordynację wielu obietnic.

W tym ćwiczeniu przyjrzymy się trzem kluczowym statycznym metodom obiektu Promise:

  • Promise.all(): do wykonywania operacji równoległych i oczekiwania na zakończenie wszystkich
  • Promise.race(): do reagowania na pierwszą zakończoną operację
  • Promise.allSettled(): do czekania na zakończenie wszystkich operacji, niezależnie od wyniku

Promise.all() - wszystko albo nic

Metoda

Promise.all()
przyjmuje tablicę obietnic i zwraca nową obietnicę, która:

  • rozwiązuje się, gdy wszystkie obietnice w tablicy zostaną spełnione, z tablicą wyników
  • odrzuca się, gdy którakolwiek z obietnic zostanie odrzucona, z pierwszym błędem

Jest to idealne narzędzie, gdy musisz wykonać wiele niezależnych operacji równolegle i potrzebujesz wszystkich wyników, aby kontynuować.

Przykład: Przygotowanie parku do otwarcia

Zanim Park Jurajski może zostać otwarty dla zwiedzających, musimy sprawdzić różne systemy równocześnie:

1// Funkcje zwracające Promise dla różnych systemów
2function sprawdźSystemyBezpieczeństwa() {
3  console.log("Sprawdzanie systemów bezpieczeństwa...");
4  return new Promise((resolve, reject) => {
5    setTimeout(() => {
6      const systemySprawne = Math.random() > 0.1; // 10% szans na błąd
7      if (systemySprawne) {
8        resolve({
9          status: "OK",
10          message: "Wszystkie systemy bezpieczeństwa działają prawidłowo",
11          lastChecked: new Date().toISOString()
12        });
13      } else {
14        reject(new Error("Awaryjne ogrodzenie w sektorze B nie działa!"));
15      }
16    }, 2000);
17  });
18}
19
20function sprawdźZdrowieDinozaurów() {
21  console.log("Sprawdzanie zdrowia dinozaurów...");
22  return new Promise((resolve) => {
23    setTimeout(() => {
24      resolve({
25        status: "OK",
26        message: "Wszystkie dinozaury zdrowe",
27        count: 36,
28        sick: 0
29      });
30    }, 3000);
31  });
32}
33
34function sprawdźSystemyZasilania() {
35  console.log("Sprawdzanie systemów zasilania...");
36  return new Promise((resolve) => {
37    setTimeout(() => {
38      resolve({
39        status: "OK",
40        message: "Zasilanie główne i zapasowe działa prawidłowo",
41        mainPower: "online",
42        backupPower: "standby"
43      });
44    }, 1500);
45  });
46}
47
48function sprawdźPogodę() {
49  console.log("Sprawdzanie prognozy pogody...");
50  return new Promise((resolve, reject) => {
51    setTimeout(() => {
52      const pogodaOK = Math.random() > 0.2; // 20% szans na złą pogodę
53      if (pogodaOK) {
54        resolve({
55          status: "OK",
56          message: "Pogoda sprzyjająca",
57          forecast: "Słonecznie, 25°C"
58        });
59      } else {
60        reject(new Error("Nadchodzi burza tropikalna! Otwarcie parku niemożliwe."));
61      }
62    }, 1000);
63  });
64}
65
66// Funkcja główna do sprawdzenia gotowości parku
67function sprawdźGotowośćParku() {
68  console.log("Rozpoczynam sprawdzanie gotowości parku do otwarcia...");
69
70  const startTime = Date.now();
71
72  // Uruchamiamy wszystkie sprawdzenia równolegle
73  const bezpieczeństwo = sprawdźSystemyBezpieczeństwa();
74  const dinozaury = sprawdźZdrowieDinozaurów();
75  const zasilanie = sprawdźSystemyZasilania();
76  const pogoda = sprawdźPogodę();
77
78  // Używamy Promise.all, aby poczekać na wszystkie wyniki
79  return Promise.all([bezpieczeństwo, dinozaury, zasilanie, pogoda])
80    .then(wyniki => {
81      const endTime = Date.now();
82      const czasWykonania = (endTime - startTime) / 1000;
83
84      // Rozpakowanie wyników
85      const [wynikBezpieczeństwa, wynikDinozaurów, wynikZasilania, wynikPogody] = wyniki;
86
87      return {
88        status: "PARK GOTOWY DO OTWARCIA",
89        bezpieczeństwo: wynikBezpieczeństwa,
90        dinozaury: wynikDinozaurów,
91        zasilanie: wynikZasilania,
92        pogoda: wynikPogody,
93        czasSprawdzenia: `${czasWykonania} sekund`
94      };
95    })
96    .catch(error => {
97      throw new Error(`Park nie jest gotowy do otwarcia: ${error.message}`);
98    });
99}
100
101// Używamy naszej funkcji
102sprawdźGotowośćParku()
103  .then(raport => {
104    console.log("RAPORT GOTOWOŚCI PARKU:");
105    console.log(JSON.stringify(raport, null, 2));
106
107    // Rozpocznij procedurę otwarcia bram
108    otwórzBramyParku();
109  })
110  .catch(error => {
111    console.error("BŁĄD:", error.message);
112
113    // Powiadom kierownictwo o problemie
114    powiadomKierownictwo(error.message);
115  });
116
117function otwórzBramyParku() {
118  console.log("Otwieranie bram parku dla zwiedzających...");
119}
120
121function powiadomKierownictwo(wiadomość) {
122  console.log(`Powiadamianie kierownictwa: ${wiadomość}`);
123}

W powyższym przykładzie:

  1. Uruchamiamy 4 niezależne sprawdzenia systemów parku równolegle
  2. Używamy
    Promise.all()
    do czekania na zakończenie wszystkich sprawdzeń
  3. Jeśli wszystkie się powiodą, otrzymujemy tablicę wyników w tej samej kolejności, co tablica obietnic
  4. Jeśli którykolwiek się nie powiedzie, cała operacja jest uznawana za nieudaną

Zauważ, że wszystkie operacje są uruchamiane jednocześnie (nie jedna po drugiej), co znacznie przyspiesza cały proces. Zamiast czekać 2000 + 3000 + 1500 + 1000 = 7500 ms, czekamy tylko na najdłuższą operację, czyli około 3000 ms.

Obsługa wyników Promise.all()

Wynikiem spełnionej obietnicy zwróconej przez

Promise.all()
jest tablica wyników w tej samej kolejności, co wejściowa tablica obietnic. Możemy użyć destrukturyzacji tablicy, aby elegancko przypisać nazwy do poszczególnych wyników:

1Promise.all([pobierzDino(1), pobierzDino(2), pobierzDino(3)])
2  .then(([dino1, dino2, dino3]) => {
3    console.log("T-Rex:", dino1.name);
4    console.log("Velociraptor:", dino2.name);
5    console.log("Triceratops:", dino3.name);
6  });

Kiedy nie używać Promise.all()

Trzeba pamiętać, że

Promise.all()
stosuje podejście "wszystko albo nic". Jeśli którakolwiek obietnica zostanie odrzucona, cała operacja kończy się niepowodzeniem. Nie jest to zawsze pożądane zachowanie.

Promise.race() - odpowiedź na pierwszą zakończoną operację

Metoda

Promise.race()
również przyjmuje tablicę obietnic, ale zwraca nową obietnicę, która rozwiązuje się lub odrzuca z wynikiem pierwszej rozwiązanej lub odrzuconej obietnicy z tablicy.

Jest to idealne rozwiązanie dla scenariuszy, w których:

  • Potrzebujesz tylko pierwszego dostępnego wyniku
  • Chcesz ustawić timeout dla operacji
  • Potrzebujesz zaimplementować wzorzec "wyścig"

Przykład: Szybkie rozpoznawanie gatunku dinozaura

Wyobraźmy sobie, że mamy kilka różnych systemów rozpoznawania gatunków dinozaurów (wizualny, DNA, behawioralny) i chcemy użyć wyniku z tego, który zadziała najszybciej:

1function rozpoznajWizualnie(obrazDinozaura) {
2  console.log("Rozpoczynam analizę wizualną...");
3  return new Promise((resolve, reject) => {
4    setTimeout(() => {
5      if (Math.random() > 0.3) { // 70% szans na sukces
6        resolve({
7          method: "visual",
8          species: "Velociraptor",
9          confidence: 0.82,
10          timeMs: 1800
11        });
12      } else {
13        reject(new Error("Nie udało się rozpoznać wizualnie"));
14      }
15    }, 1800);
16  });
17}
18
19function rozpoznajPrzezDNA(próbkaDNA) {
20  console.log("Rozpoczynam analizę DNA...");
21  return new Promise((resolve, reject) => {
22    setTimeout(() => {
23      if (Math.random() > 0.1) { // 90% szans na sukces
24        resolve({
25          method: "dna",
26          species: "Velociraptor",
27          subspecies: "V. mongoliensis",
28          confidence: 0.98,
29          timeMs: 3500
30        });
31      } else {
32        reject(new Error("Próbka DNA zanieczyszczona"));
33      }
34    }, 3500);
35  });
36}
37
38function rozpoznajZachowanie(nagranieBehawioralne) {
39  console.log("Rozpoczynam analizę behawioralną...");
40  return new Promise((resolve, reject) => {
41    setTimeout(() => {
42      if (Math.random() > 0.4) { // 60% szans na sukces
43        resolve({
44          method: "behavioral",
45          species: "Velociraptor",
46          behaviors: ["pack-hunting", "high-agility", "vocalizations"],
47          confidence: 0.75,
48          timeMs: 2200
49        });
50      } else {
51        reject(new Error("Za mało danych behawioralnych"));
52      }
53    }, 2200);
54  });
55}
56
57// Funkcja do szybkiego rozpoznania dinozaura
58function szybkieRozpoznanie(dane) {
59  console.log("Uruchamiam szybkie rozpoznanie dinozaura...");
60
61  return Promise.race([
62    rozpoznajWizualnie(dane.obraz),
63    rozpoznajPrzezDNA(dane.dna),
64    rozpoznajZachowanie(dane.nagranie)
65  ]);
66}
67
68// Użycie
69const daneDinozaura = {
70  obraz: "https://jurassic-world.com/images/suspect_dino.jpg",
71  dna: "ACGTTTCGAACGTACGATCGATCG...",
72  nagranie: "https://jurassic-world.com/videos/suspect_dino.mp4"
73};
74
75szybkieRozpoznanie(daneDinozaura)
76  .then(wynik => {
77    console.log(`Dinozaur rozpoznany jako: ${wynik.species}`);
78    console.log(`Metoda: ${wynik.method}, Pewność: ${wynik.confidence * 100}%`);
79    console.log(`Czas rozpoznania: ${wynik.timeMs} ms`);
80
81    // Podejmij działania na podstawie gatunku
82    if (wynik.species === "Velociraptor") {
83      podwyższBezpieczeństwoWybiegu("RAPTOR-PADDOCK");
84    }
85  })
86  .catch(error => {
87    console.error("Nie udało się rozpoznać dinozaura:", error.message);
88    console.log("Uruchamiam procedurę bezpieczeństwa dla nieznanego gatunku...");
89    proceduraBezpieczeństwaDlaNieznanego();
90  });
91
92function podwyższBezpieczeństwoWybiegu(id) {
93  console.log(`Podwyższanie poziomu bezpieczeństwa wybiegu ${id}...`);
94}
95
96function proceduraBezpieczeństwaDlaNieznanego() {
97  console.log("Aktywacja protokołu dla nieznanego gatunku...");
98}

W tym przykładzie uruchamiamy trzy metody rozpoznawania równolegle i używamy pierwszego dostępnego wyniku. Metoda wizualna jest najszybsza (1800 ms), ale ma najniższą dokładność. Metoda DNA jest najwolniejsza (3500 ms), ale najbardziej dokładna. Metoda behawioralna jest pośrodku. Dzięki

Promise.race()
otrzymujemy pierwszy dostępny wynik, co pozwala na szybką reakcję.

Implementacja timeoutu za pomocą Promise.race()

Często potrzebujemy przerwać długotrwające operacje, jeśli trwają zbyt długo.

Promise.race()
jest idealnym narzędziem do implementacji timeoutu:

1// Funkcja pomocnicza tworząca obietnicę timeoutu
2function timeout(ms, message) {
3  return new Promise((_, reject) => {
4    setTimeout(() => {
5      reject(new Error(`Timeout po ${ms}ms: ${message}`));
6    }, ms);
7  });
8}
9
10// Funkcja z timeoutem
11function pobierzDaneDinozauraZTimeoutem(id, czasTimeout) {
12  console.log(`Pobieranie danych dinozaura #${id} z timeoutem ${czasTimeout}ms...`);
13
14  // Właściwa operacja
15  const pobieranieDanych = pobierzDaneDinozaura(id);
16
17  // Obietnica timeoutu
18  const timeoutPromise = timeout(czasTimeout, `Pobieranie danych dinozaura #${id}`);
19
20  // Wyścig między pobieraniem danych a timeoutem
21  return Promise.race([pobieranieDanych, timeoutPromise]);
22}
23
24// Przykładowa funkcja pobierająca dane
25function pobierzDaneDinozaura(id) {
26  return new Promise((resolve) => {
27    // Symulacja czasu odpowiedzi od 1s do 6s
28    const czas = 1000 + Math.random() * 5000;
29    setTimeout(() => {
30      resolve({
31        id: id,
32        name: `Dino-${id}`,
33        species: id % 2 === 0 ? "Tyrannosaurus" : "Velociraptor",
34        age: Math.floor(Math.random() * 20) + 1
35      });
36    }, czas);
37  });
38}
39
40// Użycie funkcji z timeoutem
41pobierzDaneDinozauraZTimeoutem(42, 3000)
42  .then(dino => {
43    console.log("Otrzymane dane dinozaura:", dino);
44  })
45  .catch(error => {
46    console.error("Błąd:", error.message);
47    console.log("Podejmuję działania awaryjne...");
48  });

W tym przykładzie, jeśli

pobierzDaneDinozaura()
zajmie więcej niż 3 sekundy, operacja zostanie przerwana przez timeout, a Promise zostanie odrzucony z błędem timeoutu.

Promise.allSettled() - pełna informacja o wszystkich operacjach

Metoda

Promise.allSettled()
(wprowadzona w ES2020) przyjmuje tablicę obietnic i zwraca nową obietnicę, która zawsze rozwiązuje się po zakończeniu wszystkich obietnic w tablicy, niezależnie od tego, czy zostały spełnione, czy odrzucone.

Wynikiem jest tablica obiektów, gdzie każdy obiekt opisuje wynik odpowiedniej obietnicy:

  • Dla spełnionych obietnic:
    { status: 'fulfilled', value: wynik }
  • Dla odrzuconych obietnic:
    { status: 'rejected', reason: błąd }

Jest to doskonałe rozwiązanie, gdy:

  • Potrzebujesz wiedzieć o wszystkich wynikach, niezależnie od sukcesu czy porażki
  • Operacje są niezależne i niepowodzenie jednej nie powinno wpływać na inne
  • Chcesz mieć pełny raport o stanie wszystkich operacji

Przykład: Monitorowanie wszystkich systemów parku

W Parku Jurajskim musimy regularnie monitorować wszystkie systemy i raporty powinny zawierać informacje o każdym systemie, nawet jeśli niektóre zgłaszają błędy:

1function monitorujWszystkieSystemy() {
2  console.log("Rozpoczynam monitorowanie wszystkich systemów parku...");
3
4  // Tablica obietnic dla wszystkich systemów
5  const monitorowanieSystemów = [
6    monitorujOgrodzenie(),
7    monitorujZasilanie(),
8    monitorujKamery(),
9    monitorujCzujnikiRuchu(),
10    monitorujSystemWodny(),
11    monitorujWentylację(),
12    monitorujTemperaturę(),
13    monitorujBramyWybiegów()
14  ];
15
16  // Użycie Promise.allSettled
17  return Promise.allSettled(monitorowanieSystemów)
18    .then(wyniki => {
19      // Przygotowanie raportu
20      const raport = {
21        czasMonitorowania: new Date().toISOString(),
22        liczbaSprawnychSystemów: 0,
23        liczbaSystemówZProblemi: 0,
24        systemySprawne: [],
25        systemyZProblemami: []
26      };
27
28      // Analiza wyników
29      wyniki.forEach((wynik, index) => {
30        const nazwaSystemu = pobierzNazwęSystemu(index);
31
32        if (wynik.status === 'fulfilled') {
33          raport.liczbaSprawnychSystemów++;
34          raport.systemySprawne.push({
35            nazwa: nazwaSystemu,
36            status: wynik.value.status,
37            szczegóły: wynik.value.details
38          });
39        } else {
40          raport.liczbaSystemówZProblemi++;
41          raport.systemyZProblemami.push({
42            nazwa: nazwaSystemu,
43            błąd: wynik.reason.message,
44            czasWystąpienia: new Date().toISOString()
45          });
46        }
47      });
48
49      // Dodanie podsumowania
50      raport.czyWszystkieSystemySprawne = raport.liczbaSystemówZProblemi === 0;
51      raport.poziomZagrożenia = obliczPoziomZagrożenia(raport);
52
53      return raport;
54    });
55}
56
57// Funkcja pomocnicza do mapowania indeksu na nazwę systemu
58function pobierzNazwęSystemu(index) {
59  const nazwySystemów = [
60    "Ogrodzenie elektryczne",
61    "System zasilania",
62    "System kamer",
63    "Czujniki ruchu",
64    "System wodny",
65    "System wentylacji",
66    "Kontrola temperatury",
67    "Bramy wybiegów"
68  ];
69  return nazwySystemów[index];
70}
71
72// Funkcja do obliczania poziomu zagrożenia na podstawie raportu
73function obliczPoziomZagrożenia(raport) {
74  // Logika określająca poziom zagrożenia
75  if (raport.systemyZProblemami.some(s => s.nazwa === "Ogrodzenie elektryczne")) {
76    return "CRITICAL";
77  } else if (raport.liczbaSystemówZProblemi > 2) {
78    return "HIGH";
79  } else if (raport.liczbaSystemówZProblemi > 0) {
80    return "MEDIUM";
81  } else {
82    return "LOW";
83  }
84}
85
86// Przykładowe implementacje funkcji monitorujących
87function monitorujOgrodzenie() {
88  return new Promise((resolve, reject) => {
89    setTimeout(() => {
90      if (Math.random() > 0.9) { // 10% szans na błąd
91        reject(new Error("Wykryto awarię w sektorze ogrodzenia A3"));
92      } else {
93        resolve({ status: "OK", details: { voltage: "10000V", lastCheck: new Date() } });
94      }
95    }, 1000 + Math.random() * 1000);
96  });
97}
98
99function monitorujZasilanie() {
100  return new Promise((resolve) => {
101    setTimeout(() => {
102      resolve({
103        status: "OK",
104        details: {
105          mainPower: "ONLINE",
106          backupPower: "STANDBY",
107          powerConsumption: "75%"
108        }
109      });
110    }, 800 + Math.random() * 1000);
111  });
112}
113
114function monitorujKamery() {
115  return new Promise((resolve, reject) => {
116    setTimeout(() => {
117      if (Math.random() > 0.8) { // 20% szans na błąd
118        reject(new Error("Kamera nr 12 w sektorze B nie odpowiada"));
119      } else {
120        resolve({
121          status: "OK",
122          details: {
123            onlineCameras: 36,
124            offlineCameras: 0
125          }
126        });
127      }
128    }, 1200 + Math.random() * 800);
129  });
130}
131
132// Implementacje pozostałych funkcji monitorujących...
133function monitorujCzujnikiRuchu() {
134  return Promise.resolve({ status: "OK", details: { sensorsActive: 48 } });
135}
136
137function monitorujSystemWodny() {
138  return Promise.resolve({ status: "OK", details: { pressure: "Normal", quality: "Good" } });
139}
140
141function monitorujWentylację() {
142  return Promise.resolve({ status: "OK", details: { airQuality: "Excellent" } });
143}
144
145function monitorujTemperaturę() {
146  return Promise.resolve({ status: "OK", details: { avgTemp: "22°C" } });
147}
148
149function monitorujBramyWybiegów() {
150  return Promise.resolve({ status: "OK", details: { allGatesClosed: true } });
151}
152
153// Użycie funkcji monitorującej
154monitorujWszystkieSystemy()
155  .then(raport => {
156    console.log("RAPORT MONITOROWANIA SYSTEMÓW:");
157    console.log(JSON.stringify(raport, null, 2));
158
159    // Podejmowanie działań na podstawie raportu
160    if (raport.poziomZagrożenia === "CRITICAL") {
161      console.log("UWAGA! KRYTYCZNY POZIOM ZAGROŻENIA! Ewakuacja parku!");
162      ewakuujPark();
163    } else if (raport.poziomZagrożenia === "HIGH") {
164      console.log("UWAGA! WYSOKI POZIOM ZAGROŻENIA! Wstrzymanie wstępu gości!");
165      wstrzymajWstęp();
166    } else if (raport.systemyZProblemami.length > 0) {
167      console.log("Wykryto problemy w niektórych systemach. Wysyłanie ekip technicznych...");
168      wyślijEkipyTechniczne(raport.systemyZProblemami);
169    } else {
170      console.log("Wszystkie systemy działają prawidłowo.");
171    }
172  });
173
174// Funkcje do obsługi różnych poziomów zagrożenia
175function ewakuujPark() {
176  console.log("Rozpoczynanie procedury ewakuacji parku...");
177}
178
179function wstrzymajWstęp() {
180  console.log("Wstrzymywanie wstępu nowych gości do parku...");
181}
182
183function wyślijEkipyTechniczne(systemyZProblemami) {
184  console.log(`Wysyłanie ekip technicznych do ${systemyZProblemami.length} systemów z problemami...`);
185  systemyZProblemami.forEach(system => {
186    console.log(`- Wysyłanie techników do systemu: ${system.nazwa} (błąd: ${system.błąd})`);
187  });
188}

W tym przykładzie

Promise.allSettled()
pozwala nam zebrać informacje o wszystkich systemach parku, niezależnie od tego, czy są sprawne czy nie. Dzięki temu możemy przygotować kompleksowy raport i podjąć odpowiednie działania na podstawie ogólnego stanu systemów.

Promise.allSettled() vs. Promise.all()

Porównajmy zachowanie

Promise.allSettled()
z
Promise.all()
:

1// Tablica zawierająca obiektnice, z których niektóre są odrzucone
2const mieszanePromises = [
3  Promise.resolve("Sukces 1"),
4  Promise.reject(new Error("Błąd 1")),
5  Promise.resolve("Sukces 2"),
6  Promise.reject(new Error("Błąd 2"))
7];
8
9// Używając Promise.all
10Promise.all(mieszanePromises)
11  .then(results => {
12    console.log("Wszystkie powiodły się:", results);
13    // Ten kod nigdy się nie wykona z powodu odrzuconych Promise w tablicy
14  })
15  .catch(error => {
16    console.error("Promise.all złapał błąd:", error.message);
17    // Wyświetli: "Promise.all złapał błąd: Błąd 1"
18    // Zauważ, że otrzymujemy tylko pierwszy błąd, a nie wszystkie
19  });
20
21// Używając Promise.allSettled
22Promise.allSettled(mieszanePromises)
23  .then(results => {
24    console.log("Wyniki wszystkich Promise:");
25
26    // Przetwarzamy wszystkie wyniki
27    results.forEach((result, index) => {
28      if (result.status === 'fulfilled') {
29        console.log(`Promise ${index}: OK - ${result.value}`);
30      } else {
31        console.log(`Promise ${index}: BŁĄD - ${result.reason.message}`);
32      }
33    });
34
35    // Wyświetli:
36    // "Promise 0: OK - Sukces 1"
37    // "Promise 1: BŁĄD - Błąd 1"
38    // "Promise 2: OK - Sukces 2"
39    // "Promise 3: BŁĄD - Błąd 2"
40  });

Kluczowa różnica:

Promise.all()
kończy się niepowodzeniem przy pierwszym błędzie, podczas gdy
Promise.allSettled()
zawsze kończy się sukcesem z informacjami o wszystkich wynikach.

Promise.any() - sukces, gdy którakolwiek się powiedzie

Metoda

Promise.any()
(wprowadzona w ES2021) przyjmuje tablicę obietnic i zwraca nową obietnicę, która:

  • rozwiązuje się z wartością pierwszej spełnionej obietnicy
  • odrzuca się tylko wtedy, gdy wszystkie obietnice zostaną odrzucone, z obiektem AggregateError zawierającym wszystkie błędy

Jest to podobne do

Promise.race()
, ale z jedną kluczową różnicą:
Promise.any()
ignoruje odrzucone obietnice, czekając na pierwszą spełnioną obietnicę.

Przykład: Testowanie różnych bram ewakuacyjnych

1function testBramyEwakuacyjnej(id) {
2  console.log(`Testowanie bramy ewakuacyjnej #${id}...`);
3  return new Promise((resolve, reject) => {
4    setTimeout(() => {
5      const działa = Math.random() > 0.6; // 40% szans na awarię
6      if (działa) {
7        resolve({
8          id: id,
9          status: "Operacyjna",
10          czasOtwarcia: `${(Math.random() * 2 + 1).toFixed(1)}s`
11        });
12      } else {
13        reject(new Error(`Brama #${id} nie reaguje na sygnał otwarcia`));
14      }
15    }, 1000 + Math.random() * 2000);
16  });
17}
18
19// Próba otwarcia dowolnej działającej bramy
20function znajdźDziałającąBramę() {
21  console.log("Próba otwarcia dowolnej działającej bramy ewakuacyjnej...");
22
23  // Testujemy wszystkie bramy równocześnie
24  return Promise.any([
25    testBramyEwakuacyjnej(1),
26    testBramyEwakuacyjnej(2),
27    testBramyEwakuacyjnej(3),
28    testBramyEwakuacyjnej(4)
29  ]);
30}
31
32// Użycie
33znajdźDziałającąBramę()
34  .then(działającaBrama => {
35    console.log(`Znaleziono działającą bramę ewakuacyjną: #${działającaBrama.id}`);
36    console.log(`Czas otwarcia: ${działającaBrama.czasOtwarcia}`);
37    console.log("Rozpoczynanie ewakuacji przez tę bramę...");
38  })
39  .catch(error => {
40    // Ten catch zostanie wykonany tylko wtedy, gdy wszystkie bramy zawiodą
41    console.error("KRYTYCZNY BŁĄD: Wszystkie bramy ewakuacyjne nie działają!");
42    console.error("Szczegóły:", error.errors.map(e => e.message).join(", "));
43    console.log("Uruchamianie procedury awaryjnej wyłamania bram...");
44  });

W tym przykładzie

Promise.any()
pozwala nam znaleźć pierwszą działającą bramę ewakuacyjną i natychmiast rozpocząć przez nią ewakuację, ignorując bramy, które nie działają. Tylko gdy wszystkie bramy zawodzą, całość kończy się niepowodzeniem.

Wybór odpowiedniej metody do kompozycji Promise

Podsumowując, mamy cztery główne metody do kompozycji Promise:

  1. Promise.all()

    • Użyj gdy: Potrzebujesz wszystkich wyników i wszystkie operacje muszą się powieść
    • Zachowanie: Wszystko albo nic
    • Wynik: Tablica wszystkich wyników (gdy wszystkie powiodą się)
    • Błąd: Pierwszy błąd (gdy którakolwiek zawiedzie)
  2. Promise.race()

    • Użyj gdy: Potrzebujesz tylko pierwszego wyniku (sukces lub błąd) - np. do timeoutów
    • Zachowanie: Pierwszy wygrywa (sukces lub błąd)
    • Wynik: Pierwszy sukces lub pierwszy błąd (cokolwiek nastąpi pierwsze)
  3. Promise.allSettled()

    • Użyj gdy: Potrzebujesz informacji o wszystkich operacjach, niezależnie od wyniku
    • Zachowanie: Czeka na zakończenie wszystkich (zawsze kończy się sukcesem)
    • Wynik: Tablica obiektów statusu dla wszystkich obietnic (sukces lub błąd)
  4. Promise.any()

    • Użyj gdy: Potrzebujesz pierwszego udanego wyniku, ignorując błędy
    • Zachowanie: Pierwszy sukces wygrywa
    • Wynik: Pierwszy udany wynik
    • Błąd: AggregateError zawierający wszystkie błędy (tylko gdy wszystkie zawiodą)

Wybór metody na podstawie scenariusza

Poniższa tabelka pomoże Ci wybrać odpowiednią metodę w zależności od Twoich potrzeb:

| Scenariusz | Zalecana metoda | |------------|----------------| | Musisz wykonać wiele zadań, gdzie wszystkie muszą się powieść (np. sprawdzenie gotowości systemu) |

Promise.all()
| | Potrzebujesz pierwszego dostępnego wyniku, nieważne którego (np. wyścig zasobów) |
Promise.race()
| | Chcesz ustawić timeout dla operacji |
Promise.race()
z timeoutem | | Potrzebujesz pełnego raportu o wszystkich operacjach, nawet jeśli niektóre zawiodły |
Promise.allSettled()
| | Potrzebujesz pierwszego udanego wyniku, ignorując błędy (np. próba różnych źródeł danych) |
Promise.any()
|

Praktyki zaawansowane

Dynamiczne tworzenie tablicy Promise

Często musimy dynamicznie tworzyć tablicę obietnic na podstawie danych:

1// Pobieranie danych o wszystkich dinozaurach w parku
2function pobierzDaneWszystkichDinozaurów(ids) {
3  console.log(`Pobieranie danych dla ${ids.length} dinozaurów...`);
4
5  // Mapowanie ID na obietnice
6  const promises = ids.map(id => pobierzDaneDinozaura(id));
7
8  // Użycie Promise.all do równoległego pobierania
9  return Promise.all(promises);
10}
11
12// Użycie
13const idDinozaurów = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
14
15pobierzDaneWszystkichDinozaurów(idDinozaurów)
16  .then(dinozaury => {
17    console.log(`Pobrano dane ${dinozaury.length} dinozaurów`);
18
19    // Obliczanie statystyk
20    const mięsożerne = dinozaury.filter(d => d.diet === "carnivore").length;
21    const roślinożerne = dinozaury.filter(d => d.diet === "herbivore").length;
22    const wszystkożerne = dinozaury.filter(d => d.diet === "omnivore").length;
23
24    console.log(`Statystyki: ${mięsożerne} mięsożernych, ${roślinożerne} roślinożernych, ${wszystkożerne} wszystkożernych`);
25  })
26  .catch(error => {
27    console.error("Błąd pobierania danych:", error.message);
28  });

Łączenie różnych metod kompozycji Promise

Możemy łączyć różne metody kompozycji Promise, aby uzyskać bardziej złożone zachowania:

1function monitorujDinozaury() {
2  // Pobieramy wszystkie sektory parku
3  return pobierzWszystkieSektory()
4    .then(sektory => {
5      // Dla każdego sektora równolegle sprawdzamy dinozaury
6      const obietniceSektorów = sektory.map(sektor => {
7        // Dla każdego sektora używamy allSettled, aby mieć informacje o wszystkich dinozaurach
8        const obietniceDinozaurów = sektor.dinozaury.map(dino =>
9          sprawdźStatusDinozaura(dino.id)
10            .catch(error => ({
11              status: "error",
12              dinoId: dino.id,
13              message: error.message
14            }))
15        );
16
17        // Używamy allSettled dla dinozaurów w obrębie sektora
18        return Promise.allSettled(obietniceDinozaurów)
19          .then(wyniki => ({
20            sektor: sektor.id,
21            nazwa: sektor.name,
22            wyniki: wyniki
23          }));
24      });
25
26      // Używamy all dla sektorów, bo potrzebujemy danych z wszystkich
27      return Promise.all(obietniceSektorów);
28    });
29}

Obsługa zbyt wielu równoległych zapytań

Kiedy mamy do wykonania wiele operacji asynchronicznych, nie zawsze dobrym pomysłem jest uruchamianie ich wszystkich równocześnie - może to przeciążyć system. Możemy zaimplementować mechanizm ograniczający liczbę równoległych operacji:

1// Funkcja wykonująca zadania z ograniczeniem równoległości
2async function wykonajZadaniaZOgraniczeniem(zadania, maxRównoległe) {
3  const wyniki = [];
4
5  // Przetwarzamy zadania w porcjach
6  for (let i = 0; i < zadania.length; i += maxRównoległe) {
7    const porcja = zadania.slice(i, i + maxRównoległe);
8
9    // Wykonujemy porcję zadań równolegle
10    const wynikPorcji = await Promise.allSettled(porcja.map(zadanie => zadanie()));
11    wyniki.push(...wynikPorcji);
12
13    console.log(`Zakończono porcję ${Math.floor(i / maxRównoległe) + 1}/${Math.ceil(zadania.length / maxRównoległe)}`);
14  }
15
16  return wyniki;
17}
18
19// Przykład użycia
20const zadaniaDinozaurów = Array.from({ length: 50 }, (_, i) => {
21  return () => sprawdźStatusDinozaura(i + 1);
22});
23
24// Wykonujemy maksymalnie 5 zadań równocześnie
25wykonajZadaniaZOgraniczeniem(zadaniaDinozaurów, 5)
26  .then(wyniki => {
27    console.log(`Zakończono sprawdzanie wszystkich ${wyniki.length} dinozaurów`);
28    // Analiza wyników...
29  });

Podsumowanie

Statyczne metody Promise (

Promise.all()
,
Promise.race()
,
Promise.allSettled()
i
Promise.any()
) zapewniają potężne narzędzia do kompozycji i koordynacji wielu operacji asynchronicznych. Wybór odpowiedniej metody zależy od konkretnych wymagań Twojej aplikacji:

  • Promise.all() - gdy potrzebujesz wszystkich wyników i wszystkie operacje muszą się powieść
  • Promise.race() - gdy potrzebujesz pierwszego wyniku lub chcesz zaimplementować timeout
  • Promise.allSettled() - gdy potrzebujesz informacji o wszystkich operacjach, niezależnie od wyniku
  • Promise.any() - gdy potrzebujesz pierwszego udanego wyniku, ignorując błędy

W Parku Jurajskim, jak i w każdej złożonej aplikacji JavaScript, umiejętność efektywnego komponowania operacji asynchronicznych jest kluczowa dla tworzenia responsywnych, wydajnych i niezawodnych systemów.

W następnym ćwiczeniu poznamy składnię

async/await
, która zapewnia jeszcze bardziej czytelny sposób pracy z asynchronicznym kodem.

Vai a CodeWorlds