W poprzednich ćwiczeniach poznaliśmy Promisy oraz metody ich składania, które znacznie ułatwiają pracę z kodem asynchronicznym. Jednak JavaScript oferuje jeszcze bardziej elegancką i czytelną składnię do obsługi asynchroniczności: async/await.
Składnia async/await, wprowadzona w ECMAScript 2017 (ES8), działa na bazie Promisów, ale pozwala pisać kod asynchroniczny w sposób, który wygląda jak synchroniczny. Dzięki temu nasz kod staje się bardziej czytelny i łatwiejszy w utrzymaniu - co jest niezwykle ważne przy zarządzaniu tak skomplikowanym przedsięwzięciem jak Park Jurajski!
Składnia async/await składa się z dwóch głównych elementów:
Zobaczmy, jak wygląda prosta funkcja async:
1// Funkcja tradycyjna zwracająca Promise
2function pobierzDinozaura(id) {
3 return new Promise((resolve, reject) => {
4 setTimeout(() => {
5 if (id > 0 && id <= 100) {
6 resolve({
7 id: id,
8 name: `Dino-${id}`,
9 species: id % 2 === 0 ? "Tyrannosaurus" : "Velociraptor",
10 health: "Good"
11 });
12 } else {
13 reject(new Error(`Dinozaur o ID ${id} nie istnieje`));
14 }
15 }, 1000);
16 });
17}
18
19// Funkcja async pobierająca dane dinozaura
20async function pobierzDaneDinozaura(id) {
21 console.log(`Rozpoczynam pobieranie danych dinozaura #${id}...`);
22
23 // Używamy await do "czekania" na wynik Promisa
24 const dinozaur = await pobierzDinozaura(id);
25
26 console.log(`Pobrano dane dinozaura: ${dinozaur.name}`);
27 return dinozaur;
28}
29
30// Wywołanie funkcji async
31pobierzDaneDinozaura(42)
32 .then(dino => {
33 console.log("Otrzymane dane:", dino);
34 })
35 .catch(error => {
36 console.error("Wystąpił błąd:", error.message);
37 });Oto, co dzieje się w powyższym kodzie:
pobierzDaneDinozaura() ze słowem kluczowym async, co oznacza, że będzie ona zawsze zwracać Promiseawait przed wywołaniem pobierzDinozaura(id), co powoduje, że wykonanie funkcji zostanie wstrzymane do czasu rozwiązania Promisedinozaur zostanie przypisana rozwiązana wartość Promise.then() i .catch() przy wywołaniu pobierzDaneDinozaura()Porównajmy te same funkcjonalności, zaimplementowane z użyciem tradycyjnej składni Promise oraz async/await:
1// Z użyciem tradycyjnych Promise
2function sprawdźSystemyBezpieczeństwa() {
3 console.log("Sprawdzanie systemów bezpieczeństwa...");
4
5 return pobierzStatusBram()
6 .then(statusBram => {
7 console.log("Status bram:", statusBram.allClosed ? "Wszystkie zamknięte" : "UWAGA! Niektóre otwarte");
8
9 return pobierzStatusOgrodzenia();
10 })
11 .then(statusOgrodzenia => {
12 console.log("Ogrodzenie:", statusOgrodzenia.power > 8000 ? "Zabezpieczone" : "Niewystarczające zasilanie!");
13
14 return pobierzStatusKamer();
15 })
16 .then(statusKamer => {
17 console.log("Kamery:", statusKamer.allOperational ? "Wszystkie działają" : "Niektóre nie działają!");
18
19 return {
20 brama: statusBram,
21 ogrodzenie: statusOgrodzenia,
22 kamery: statusKamer,
23 czasSprawdzenia: new Date().toISOString()
24 };
25 })
26 .catch(error => {
27 console.error("Błąd podczas sprawdzania:", error.message);
28 throw new Error(`Sprawdzanie nie powiodło się: ${error.message}`);
29 });
30}
31
32// Z użyciem async/await
33async function sprawdźSystemyBezpieczeństwa() {
34 console.log("Sprawdzanie systemów bezpieczeństwa...");
35
36 try {
37 // Każde z poniższych wywołań wstrzymuje wykonanie funkcji
38 const statusBram = await pobierzStatusBram();
39 console.log("Status bram:", statusBram.allClosed ? "Wszystkie zamknięte" : "UWAGA! Niektóre otwarte");
40
41 const statusOgrodzenia = await pobierzStatusOgrodzenia();
42 console.log("Ogrodzenie:", statusOgrodzenia.power > 8000 ? "Zabezpieczone" : "Niewystarczające zasilanie!");
43
44 const statusKamer = await pobierzStatusKamer();
45 console.log("Kamery:", statusKamer.allOperational ? "Wszystkie działają" : "Niektóre nie działają!");
46
47 return {
48 brama: statusBram,
49 ogrodzenie: statusOgrodzenia,
50 kamery: statusKamer,
51 czasSprawdzenia: new Date().toISOString()
52 };
53 } catch (error) {
54 console.error("Błąd podczas sprawdzania:", error.message);
55 throw new Error(`Sprawdzanie nie powiodło się: ${error.message}`);
56 }
57}Zauważ, że:
statusBram, statusOgrodzenia, statusKamer)try/catch zamiast .catch()Każdą funkcję, która zwraca Promise, możemy przekształcić na funkcję async:
1// Funkcja zwracająca Promise
2function pomiarTemperaturyWybiegu(wybiegId) {
3 return new Promise((resolve, reject) => {
4 // Kod pomiarowy...
5 });
6}
7
8// Przekształcenie na funkcję async
9async function pomiarTemperaturyWybiegu(wybiegId) {
10 // Możemy użyć operacji asynchronicznych z await
11 const czujnik = await połączZCzujnikiem(wybiegId);
12 const odczyt = await czujnik.pobierzTemperaturę();
13
14 if (odczyt.status === "error") {
15 throw new Error(`Błąd odczytu temperatury: ${odczyt.message}`);
16 }
17
18 return {
19 wybiegId,
20 temperatura: odczyt.value,
21 jednostka: "Celsius",
22 czasPomiaru: new Date()
23 };
24}Jedną rzeczą, na którą trzeba uważać przy używaniu async/await, jest sekwencyjność. Używając
await przed każdym wywołaniem funkcji asynchronicznej, wykonujemy je jedna po drugiej, co nie zawsze jest pożądane.Na przykład, w poniższym kodzie operacje są wykonywane sekwencyjnie, jedna po drugiej:
1async function inicjalizacjaSystemówParkuSekwencyjnie() {
2 console.log("Inicjalizacja systemów parku...");
3
4 const systemWodny = await inicjalizujSystemWodny(); // 3 sekundy
5 const systemZasilania = await inicjalizujSystemZasilania(); // 2 sekundy
6 const systemBezpieczeństwa = await inicjalizujSystemBezpieczeństwa(); // 4 sekundy
7
8 // Łączny czas: około 9 sekund
9 return {
10 systemWodny,
11 systemZasilania,
12 systemBezpieczeństwa,
13 status: "Wszystkie systemy gotowe"
14 };
15}Jeśli operacje są niezależne od siebie, lepiej wykonać je równolegle:
1async function inicjalizacjaSystemówParkuRównolegle() {
2 console.log("Inicjalizacja systemów parku...");
3
4 // Uruchamiamy wszystkie operacje jednocześnie (bez await)
5 const obietnicaSystemuWodnego = inicjalizujSystemWodny();
6 const obietnicaSystemuZasilania = inicjalizujSystemZasilania();
7 const obietnicaSystemuBezpieczeństwa = inicjalizujSystemBezpieczeństwa();
8
9 // Czekamy na zakończenie wszystkich operacji
10 const [systemWodny, systemZasilania, systemBezpieczeństwa] = await Promise.all([
11 obietnicaSystemuWodnego,
12 obietnicaSystemuZasilania,
13 obietnicaSystemuBezpieczeństwa
14 ]);
15
16 // Łączny czas: około 4 sekundy (najdłuższa operacja)
17 return {
18 systemWodny,
19 systemZasilania,
20 systemBezpieczeństwa,
21 status: "Wszystkie systemy gotowe"
22 };
23}W powyższym przykładzie wszystkie trzy inicjalizacje systemów są uruchamiane równolegle, a następnie używamy
Promise.all() z await, aby poczekać na zakończenie wszystkich operacji. Dzięki temu możemy zredukować czas wykonania do czasu najdłuższej operacji, zamiast sumy czasów wszystkich operacji.Jak już widzieliśmy, jedną z głównych zalet składni async/await jest możliwość używania standardowej składni
try/catch do obsługi błędów. Jest to znacznie bardziej czytelne niż łańcuchy .catch() w tradycyjnym podejściu Promise.1async function wypuśćDinozauraZWybiegu(dinoId, wybiegId) {
2 try {
3 // Sprawdzenie, czy mamy uprawnienia
4 const uprawnienia = await sprawdźUprawnienia();
5 if (!uprawnienia.canReleaseAnimals) {
6 throw new Error("Brak uprawnień do wypuszczania dinozaurów");
7 }
8
9 // Sprawdzenie statusu dinozaura
10 const statusDinozaura = await sprawdźStatusDinozaura(dinoId);
11 if (statusDinozaura.health !== "Good") {
12 throw new Error(`Dinozaur #${dinoId} nie jest w dobrym stanie zdrowia`);
13 }
14
15 // Sprawdzenie, czy wybieg jest bezpieczny
16 const statusWybiegu = await sprawdźStatusWybiegu(wybiegId);
17 if (!statusWybiegu.isSecure) {
18 throw new Error(`Wybieg #${wybiegId} nie jest bezpieczny`);
19 }
20
21 // Otwieranie bramy wybiegu
22 await otwórzBramęWybiegu(wybiegId);
23 console.log(`Brama wybiegu #${wybiegId} otwarta`);
24
25 // Wypuszczenie dinozaura
26 const wynik = await przenieśDinozaura(dinoId, wybiegId, "outdoor-area");
27 console.log(`Dinozaur #${dinoId} został wypuszczony do strefy zewnętrznej wybiegu #${wybiegId}`);
28
29 return {
30 success: true,
31 message: `Dinozaur #${dinoId} został pomyślnie wypuszczony`,
32 details: wynik
33 };
34 } catch (error) {
35 console.error("Operacja wypuszczenia dinozaura nie powiodła się:", error.message);
36
37 // Możemy podjąć próbę naprawy błędu lub wykonać operacje czyszczące
38 try {
39 await zamknijWszystkieBramy();
40 await aktywujSystemBezpieczeństwa();
41 } catch (cleanupError) {
42 console.error("Błąd podczas operacji czyszczących:", cleanupError.message);
43 }
44
45 return {
46 success: false,
47 message: `Błąd podczas wypuszczania dinozaura: ${error.message}`,
48 error: error.message
49 };
50 }
51}W powyższym przykładzie użyliśmy bloku
try/catch do obsługi wszelkich błędów, które mogą wystąpić podczas procesu wypuszczania dinozaura. Możemy również zagnieżdżać bloki try/catch, jak widać w obsłudze błędu, gdzie próbujemy wykonać operacje czyszczące, które również mogą się nie powieść.Wprowadzenie async/await umożliwiło również asynchroniczne iteracje, które są szczególnie przydatne przy pracy z kolekcjami obiektów, które wymagają operacji asynchronicznych.
1async function zbadajWszystkieDinozaury(listaId) {
2 console.log(`Rozpoczynam badanie ${listaId.length} dinozaurów...`);
3
4 const wyniki = [];
5
6 // Sekwencyjne przetwarzanie (jeden po drugim)
7 for (const id of listaId) {
8 try {
9 const wynik = await zbadajDinozaura(id);
10 console.log(`Dinozaur #${id} przebadany: ${wynik.status}`);
11 wyniki.push({
12 id,
13 success: true,
14 result: wynik
15 });
16 } catch (error) {
17 console.error(`Błąd badania dinozaura #${id}: ${error.message}`);
18 wyniki.push({
19 id,
20 success: false,
21 error: error.message
22 });
23 }
24 }
25
26 return {
27 łącznaBadań: listaId.length,
28 udanych: wyniki.filter(w => w.success).length,
29 nieudanych: wyniki.filter(w => !w.success).length,
30 wyniki
31 };
32}W powyższym przykładzie używamy
for...of z await wewnątrz pętli, co powoduje, że badania dinozaurów są wykonywane jedno po drugim. To jest odpowiednie podejście, gdy kolejność ma znaczenie lub gdy operacje muszą być wykonywane sekwencyjnie.Jeśli operacje nie muszą być wykonywane sekwencyjnie, często lepiej jest przetwarzać kolekcję równolegle:
1async function zbadajWszystkieDinozauryRównolegle(listaId) {
2 console.log(`Rozpoczynam badanie ${listaId.length} dinozaurów równolegle...`);
3
4 // Tworzenie tablicy obietnic
5 const obietnice = listaId.map(id => {
6 return zbadajDinozaura(id)
7 .then(wynik => ({
8 id,
9 success: true,
10 result: wynik
11 }))
12 .catch(error => ({
13 id,
14 success: false,
15 error: error.message
16 }));
17 });
18
19 // Równoległe wykonanie wszystkich badań
20 const wyniki = await Promise.all(obietnice);
21
22 return {
23 łącznaBadań: listaId.length,
24 udanych: wyniki.filter(w => w.success).length,
25 nieudanych: wyniki.filter(w => !w.success).length,
26 wyniki
27 };
28}W tym przypadku używamy
map() do tworzenia tablicy Promise i następnie używamy Promise.all() z await, aby poczekać na zakończenie wszystkich operacji. Dzięki temu wszystkie badania są wykonywane równolegle, co może znacznie przyspieszyć przetwarzanie dużych kolekcji.Czasami nie chcemy wykonywać wszystkich operacji jednocześnie, bo mogłoby to przeciążyć system. W takim przypadku możemy ograniczyć liczbę równoległych operacji:
1async function zbadajDinozauryZOgraniczeniem(listaId, maxRównoległe = 3) {
2 console.log(`Rozpoczynam badanie ${listaId.length} dinozaurów (max ${maxRównoległe} równolegle)...`);
3
4 const wyniki = [];
5
6 // Przetwarzanie w porcjach
7 for (let i = 0; i < listaId.length; i += maxRównoległe) {
8 const porcja = listaId.slice(i, i + maxRównoległe);
9 console.log(`Przetwarzanie porcji ${Math.floor(i / maxRównoległe) + 1} (${porcja.length} dinozaurów)...`);
10
11 // Tworzenie tablicy obietnic dla bieżącej porcji
12 const obietnicePorcji = porcja.map(id => {
13 return zbadajDinozaura(id)
14 .then(wynik => ({
15 id,
16 success: true,
17 result: wynik
18 }))
19 .catch(error => ({
20 id,
21 success: false,
22 error: error.message
23 }));
24 });
25
26 // Równoległe wykonanie badań w bieżącej porcji
27 const wynikiPorcji = await Promise.all(obietnicePorcji);
28 wyniki.push(...wynikiPorcji);
29
30 console.log(`Porcja ${Math.floor(i / maxRównoległe) + 1} zakończona.`);
31 }
32
33 return {
34 łącznaBadań: listaId.length,
35 udanych: wyniki.filter(w => w.success).length,
36 nieudanych: wyniki.filter(w => !w.success).length,
37 wyniki
38 };
39}W tym przypadku przetwarzamy listę ID w porcjach po
maxRównoległe elementów. Każda porcja jest przetwarzana równolegle, ale czekamy na zakończenie wszystkich operacji w jednej porcji przed przejściem do następnej.Słowo kluczowe
async może być również używane z funkcjami strzałkowymi:1// Tradycyjna funkcja strzałkowa zwracająca Promise
2const monitorujDinozaura = (id) => {
3 return pobierzDinozaura(id)
4 .then(dino => {
5 return aktualizujStatusDinozaura(id, { lastChecked: new Date() });
6 });
7};
8
9// Funkcja strzałkowa async
10const monitorujDinozaura = async (id) => {
11 const dino = await pobierzDinozaura(id);
12 return await aktualizujStatusDinozaura(id, { lastChecked: new Date() });
13};Możemy również używać
async w metodach klas:1class SystemMonitorowaniaDinozaurów {
2 constructor(sektor) {
3 this.sektor = sektor;
4 this.aktywny = false;
5 this.dinozaury = [];
6 }
7
8 async inicjalizuj() {
9 try {
10 // Pobieranie listy dinozaurów w sektorze
11 const lista = await pobierzListęDinozaurówWSektorze(this.sektor);
12 this.dinozaury = lista;
13
14 // Inicjalizacja czujników
15 await this.inicjalizujCzujniki();
16
17 this.aktywny = true;
18 console.log(`System monitorowania sektora ${this.sektor} zainicjalizowany`);
19
20 return true;
21 } catch (error) {
22 console.error(`Błąd inicjalizacji systemu monitorowania: ${error.message}`);
23 return false;
24 }
25 }
26
27 async inicjalizujCzujniki() {
28 // Inicjalizacja czujników dla każdego dinozaura
29 const obietnice = this.dinozaury.map(dino =>
30 inicjalizujCzujnikDlaDinozaura(dino.id)
31 );
32
33 return Promise.all(obietnice);
34 }
35
36 async monitoruj(czasMonitorowania = 60000) {
37 if (!this.aktywny) {
38 throw new Error("System monitorowania nie jest aktywny");
39 }
40
41 console.log(`Rozpoczynam monitorowanie sektora ${this.sektor} przez ${czasMonitorowania/1000} sekund...`);
42
43 // Monitorowanie przez określony czas
44 const startTime = Date.now();
45
46 while (Date.now() - startTime < czasMonitorowania) {
47 // Pobieranie statusu wszystkich dinozaurów
48 const statusy = await Promise.all(
49 this.dinozaury.map(dino => sprawdźStatusDinozaura(dino.id))
50 );
51
52 // Analiza statusów
53 const anormalneDinozaury = statusy.filter(s => s.status !== "normal");
54
55 if (anormalneDinozaury.length > 0) {
56 console.warn(`Wykryto ${anormalneDinozaury.length} dinozaurów z anormalnym statusem!`);
57
58 for (const status of anormalneDinozaury) {
59 console.warn(`Dinozaur #${status.dinoId}: ${status.status} - ${status.details}`);
60
61 // Wysłanie alertu dla każdego anormalnego statusu
62 await wyslijAlert(this.sektor, status);
63 }
64 }
65
66 // Czekamy 5 sekund przed następnym sprawdzeniem
67 await new Promise(resolve => setTimeout(resolve, 5000));
68 }
69
70 console.log(`Monitorowanie sektora ${this.sektor} zakończone.`);
71 return `Monitorowanie zakończone: ${new Date().toISOString()}`;
72 }
73}
74
75// Użycie klasy
76async function uruchomSystemMonitorowania(sektor) {
77 const system = new SystemMonitorowaniaDinozaurów(sektor);
78
79 const inicjalizacjaUdana = await system.inicjalizuj();
80 if (!inicjalizacjaUdana) {
81 console.error(`Nie udało się zainicjalizować systemu dla sektora ${sektor}`);
82 return;
83 }
84
85 // Monitorowanie przez 5 minut
86 await system.monitoruj(300000);
87
88 console.log(`Sesja monitorowania dla sektora ${sektor} zakończona`);
89}W powyższym przykładzie mamy klasę z metodami asynchronicznymi, które używają async/await do obsługi operacji asynchronicznych.
Czasami chcemy użyć async/await na najwyższym poziomie (np. w skrypcie), gdzie normalnie nie możemy używać await. W takich przypadkach możemy użyć IIFE (Immediately Invoked Function Expression) z async:
1// Natychmiast wywołana funkcja asynchroniczna
2(async () => {
3 try {
4 console.log("Uruchamianie systemu Parku Jurajskiego...");
5
6 // Inicjalizacja systemów
7 await inicjalizujWszystkieSystemy();
8
9 // Sprawdzanie statusu dinozaurów
10 const statusDinozaurów = await sprawdźWszystkieDinozaury();
11
12 // Logika na podstawie statusu
13 if (statusDinozaurów.wszystkieBezpieczne) {
14 console.log("Park jest gotowy do otwarcia dla zwiedzających.");
15 await otwórzParkDlaZwiedzających();
16 } else {
17 console.error("Park nie jest jeszcze gotowy. Wymagany przegląd bezpieczeństwa.");
18 await inicjujPrzeglądBezpieczeństwa();
19 }
20 } catch (error) {
21 console.error("Krytyczny błąd podczas uruchamiania systemu:", error);
22 await aktywujProtokolyAwaryjne();
23 }
24})();Async/await może być również używane w funkcjach rekurencyjnych:
1// Funkcja rekurencyjna do próbowania aż do powodzenia
2async function spróbujAżDoSkutku(operacja, maxPrób = 5, opóźnienie = 1000) {
3 try {
4 return await operacja();
5 } catch (error) {
6 if (maxPrób <= 1) {
7 throw new Error(`Operacja nie powiodła się po wielu próbach: ${error.message}`);
8 }
9
10 console.log(`Próba nie powiodła się (pozostało ${maxPrób-1} prób). Czekam ${opóźnienie}ms...`);
11
12 // Czekamy określony czas
13 await new Promise(resolve => setTimeout(resolve, opóźnienie));
14
15 // Rekurencyjne wywołanie z jedną próbą mniej
16 return spróbujAżDoSkutku(operacja, maxPrób - 1, opóźnienie * 1.5);
17 }
18}
19
20// Przykład użycia
21async function uruchomDinozaura(id) {
22 await spróbujAżDoSkutku(
23 async () => {
24 const dino = await pobierzDinozaura(id);
25 if (!dino.awakened) {
26 await wybudźDinozaura(id);
27 }
28 return await aktualizujStatusDinozaura(id, { status: "active" });
29 },
30 3, // maksymalnie 3 próby
31 2000 // początkowe opóźnienie 2s (potem będzie 3s, potem 4.5s)
32 );
33
34 console.log(`Dinozaur #${id} został pomyślnie uruchomiony`);
35}ECMAScript 2018 wprowadził asynchroniczne generatory i pętlę
for-await-of, które są szczególnie przydatne do przetwarzania strumieni asynchronicznych danych:1// Asynchroniczny generator dostarczający dane o dinozaurach
2async function* generatorDanychDinozaurów(sektor) {
3 const listaDinozaurów = await pobierzListęDinozaurówWSektorze(sektor);
4
5 for (const dino of listaDinozaurów) {
6 // Pobieranie szczegółowych danych dla każdego dinozaura
7 const szczegóły = await pobierzSzczegółyDinozaura(dino.id);
8
9 // Zwracamy dane dla każdego dinozaura osobno
10 yield {
11 ...dino,
12 szczegóły
13 };
14
15 // Symulujemy opóźnienie przed przejściem do następnego
16 await new Promise(resolve => setTimeout(resolve, 500));
17 }
18}
19
20// Użycie asynchronicznego generatora z for-await-of
21async function przetwórzDaneDinozaurów(sektor) {
22 console.log(`Rozpoczynam przetwarzanie danych dinozaurów z sektora ${sektor}...`);
23
24 for await (const dino of generatorDanychDinozaurów(sektor)) {
25 console.log(`Przetwarzam dane dinozaura: ${dino.name} (ID: ${dino.id})`);
26
27 // Przetwarzanie danych
28 await zapiszDaneDoRaportu(dino);
29
30 // Emisja alertu, jeśli dinozaur potrzebuje uwagi
31 if (dino.szczegóły.requiresAttention) {
32 await wyslijAlert(sektor, dino);
33 }
34 }
35
36 console.log(`Zakończono przetwarzanie danych dinozaurów z sektora ${sektor}`);
37}W powyższym przykładzie używamy asynchronicznego generatora, który dostarcza dane o dinozaurach, a następnie używamy pętli
for-await-of do przetwarzania tych danych. Jest to szczególnie przydatne, gdy mamy do czynienia z dużą ilością danych, które chcemy przetwarzać po kolei, ale gdzie każdy element wymaga operacji asynchronicznej.Składnia async/await otwiera nowe możliwości dla wzorców projektowych w JavaScript. Oto kilka użytecznych wzorców:
1async function zPonownymiPróbami(operacja, opcje = {}) {
2 const { maxPrób = 3, opóźnienie = 1000, backoffMnożnik = 2 } = opcje;
3
4 let ostatniError;
5
6 for (let próba = 1; próba <= maxPrób; próba++) {
7 try {
8 if (próba > 1) {
9 console.log(`Próba ${próba}/${maxPrób}...`);
10 }
11
12 return await operacja();
13 } catch (error) {
14 console.error(`Próba ${próba} nie powiodła się: ${error.message}`);
15 ostatniError = error;
16
17 if (próba < maxPrób) {
18 const czasOczekiwania = opóźnienie * Math.pow(backoffMnożnik, próba - 1);
19 console.log(`Oczekiwanie ${czasOczekiwania}ms przed kolejną próbą...`);
20 await new Promise(resolve => setTimeout(resolve, czasOczekiwania));
21 }
22 }
23 }
24
25 throw new Error(`Operacja nie powiodła się po ${maxPrób} próbach. Ostatni błąd: ${ostatniError.message}`);
26}
27
28// Przykład użycia
29async function podłączDoSystemuBezpieczeństwa() {
30 return zPonownymiPróbami(
31 async () => {
32 const połączenie = await nawiążPołączenieZSystememBezpieczeństwa();
33 const autentykacja = await uwierzytelnijPołączenie(połączenie);
34 return await pobierzStatusBezpieczeństwa(połączenie, autentykacja);
35 },
36 {
37 maxPrób: 5,
38 opóźnienie: 2000,
39 backoffMnożnik: 1.5
40 }
41 );
42}1async function zTimeoutem(operacja, limitCzasu, wiadomość) {
2 // Tworzymy Promise, który odrzuca się po przekroczeniu limitu czasu
3 const timeoutPromise = new Promise((_, reject) => {
4 setTimeout(() => {
5 reject(new Error(`Przekroczono limit czasu (${limitCzasu}ms): ${wiadomość}`));
6 }, limitCzasu);
7 });
8
9 // Uruchamiamy wyścig między operacją a timeoutem
10 return Promise.race([operacja(), timeoutPromise]);
11}
12
13// Przykład użycia
14async function bezpieczneOdczytywanieTemperatury(wybiegId) {
15 try {
16 const temperatura = await zTimeoutem(
17 () => odczytajTemperaturęWybiegu(wybiegId),
18 5000,
19 `Odczytywanie temperatury wybiegu #${wybiegId}`
20 );
21
22 console.log(`Temperatura wybiegu #${wybiegId}: ${temperatura}°C`);
23 return temperatura;
24 } catch (error) {
25 console.error(`Błąd odczytu temperatury: ${error.message}`);
26 return null; // Lub możemy zwrócić wartość domyślną
27 }
28}1async function zExponentialBackoff(operacja, opcje = {}) {
2 const {
3 maxPrób = 5,
4 bazoweCzekanie = 1000,
5 maksymalneCzekanie = 30000,
6 jitter = true
7 } = opcje;
8
9 let próba = 0;
10
11 while (true) {
12 try {
13 return await operacja(++próba);
14 } catch (error) {
15 if (próba >= maxPrób) {
16 throw new Error(`Operacja nie powiodła się po ${próba} próbach: ${error.message}`);
17 }
18
19 // Obliczanie czasu oczekiwania z exponential backoff
20 let czasOczekiwania = Math.min(
21 maksymalneCzekanie,
22 bazoweCzekanie * Math.pow(2, próba - 1)
23 );
24
25 // Dodanie jittera (losowość) do czasu oczekiwania
26 if (jitter) {
27 czasOczekiwania = czasOczekiwania * (0.5 + Math.random() * 0.5);
28 }
29
30 console.log(`Próba ${próba} nie powiodła się. Oczekiwanie ${Math.round(czasOczekiwania)}ms przed kolejną próbą...`);
31
32 await new Promise(resolve => setTimeout(resolve, czasOczekiwania));
33 }
34 }
35}
36
37// Przykład użycia
38async function zsynchronizujDaneZCentralą() {
39 return zExponentialBackoff(
40 async (próba) => {
41 console.log(`Próba synchronizacji #${próba}...`);
42 const dane = await pobierzAktualneDane();
43 await wyślijDaneDoCentrali(dane);
44 return `Synchronizacja ukończona: ${dane.length} rekordów`;
45 },
46 {
47 maxPrób: 10,
48 bazoweCzekanie: 2000,
49 maksymalneCzekanie: 60000
50 }
51 );
52}1async function kaskadowePrzeszukiwanie(zapytanie) {
2 console.log(`Rozpoczynam kaskadowe przeszukiwanie dla: ${zapytanie}`);
3
4 // Próbujemy najpierw w pamięci podręcznej
5 try {
6 console.log("Przeszukiwanie pamięci podręcznej...");
7 const wynikCache = await przeszukajPamięćPodręczną(zapytanie);
8 console.log("Znaleziono w pamięci podręcznej!");
9 return {
10 źródło: "cache",
11 wynik: wynikCache
12 };
13 } catch (error) {
14 console.log("Nie znaleziono w pamięci podręcznej, próbuję lokalnej bazy danych...");
15 }
16
17 // Następnie próbujemy w lokalnej bazie danych
18 try {
19 const wynikLokalne = await przeszukajLokalnąBazęDanych(zapytanie);
20 console.log("Znaleziono w lokalnej bazie danych!");
21 // Aktualizacja pamięci podręcznej
22 await aktualizujPamięćPodręczną(zapytanie, wynikLokalne);
23 return {
24 źródło: "lokalna_baza",
25 wynik: wynikLokalne
26 };
27 } catch (error) {
28 console.log("Nie znaleziono w lokalnej bazie, próbuję w centralnej bazie...");
29 }
30
31 // W końcu próbujemy w centralnej bazie danych
32 try {
33 const wynikCentralne = await przeszukajCentralnąBazęDanych(zapytanie);
34 console.log("Znaleziono w centralnej bazie danych!");
35 // Aktualizacja lokalnej bazy i pamięci podręcznej
36 await aktualizujLokalnąBazęDanych(zapytanie, wynikCentralne);
37 await aktualizujPamięćPodręczną(zapytanie, wynikCentralne);
38 return {
39 źródło: "centralna_baza",
40 wynik: wynikCentralne
41 };
42 } catch (error) {
43 throw new Error(`Nie znaleziono wyników dla zapytania: ${zapytanie}`);
44 }
45}Składnia async/await oferuje elegancki i czytelny sposób pracy z kodem asynchronicznym w JavaScript, który działa na bazie Promise. Oto główne zalety:
try/catch zamiast .catch()Pamiętaj jednak o kilku istotnych kwestiach:
await wewnątrz funkcjiawait przed każdym wywołaniem powoduje sekwencyjne wykonanie, co może nie być optymalnetry/catch lub .catch() przy wywołaniu funkcji asyncW Parku Jurajskim, jak i w każdej złożonej aplikacji JavaScript, async/await jest nieocenionym narzędziem do zarządzania operacjami asynchronicznymi, takimi jak komunikacja z systemami bezpieczeństwa, monitorowanie dinozaurów, czy koordynacja różnych podsystemów parku.
W następnym ćwiczeniu skupimy się na strategiach obsługi błędów w kodzie asynchronicznym, co jest kluczowe dla tworzenia odpornych i niezawodnych aplikacji.