W zarządzaniu Parkiem Jurajskim, gdzie dinozaury mogą uciec z wybiegów, systemy bezpieczeństwa mogą zawieść, a odwiedzający mogą znaleźć się w niebezpieczeństwie, właściwa obsługa błędów jest krytycznie ważna. W JavaScripcie, podobnie jak w procedurach bezpieczeństwa parku, musimy być przygotowani na nieoczekiwane sytuacje i wiedzieć, jak na nie reagować.
W tym ćwiczeniu skupimy się na strategiach obsługi błędów w kodzie asynchronicznym, co jest kluczowe dla tworzenia odpornych i niezawodnych aplikacji.
Wyobraź sobie, że tworzysz system monitorowania wybiegów dinozaurów. Co się stanie, jeśli czujnik temperatury przestanie działać? Albo jeśli połączenie z systemem zabezpieczeń zostanie przerwane? Bez odpowiedniej obsługi błędów, twoja aplikacja może "zawiesić się" lub, co gorsza, działać dalej na podstawie nieprawidłowych danych.
Dobra obsługa błędów:
Zanim zagłębimy się w strategie obsługi błędów, omówmy różne typy błędów, które możemy napotkać w JavaScripcie:
Występują, gdy kod zawiera nieprawidłową składnię. Są wykrywane podczas kompilacji i uniemożliwiają uruchomienie kodu.
1// Przykład błędu składniowego
2function pobierzDaneDinozaura( { // Brakuje zamykającego nawiasu
3 // kod funkcji
4}Występują, gdy próbujemy uzyskać dostęp do zmiennej lub funkcji, która nie istnieje.
1// Przykład błędu referencyjnego
2function aktualizujStatusDinozaura(id) {
3 // Zmienna 'statusy' nie została zdefiniowana
4 statusy[id] = "Aktywny";
5}Występują, gdy operacja jest wykonywana na wartości niewłaściwego typu.
1// Przykład błędu typu
2function zliczDinozaury(listaDinozaurów) {
3 // Jeśli listaDinozaurów nie jest tablicą, wystąpi TypeError
4 return listaDinozaurów.length;
5}Występują, gdy wartość numeryczna lub parametr jest poza dopuszczalnym zakresem.
1// Przykład błędu zakresu
2function ustawTemperaturęWybiegu(temperatura) {
3 // Jeśli temperatura jest zbyt wysoka, wystąpi RangeError
4 if (temperatura > 50) {
5 throw new RangeError("Temperatura wybiegu nie może przekraczać 50°C");
6 }
7 // reszta kodu
8}Występują w przypadku nieprawidłowego użycia funkcji kodowania/dekodowania URI.
1// Przykład błędu URI
2try {
3 // Próba dekodowania nieprawidłowego URI
4 decodeURIComponent('%');
5} catch (error) {
6 console.error("Nieprawidłowy URI:", error.message);
7}Występowały historycznie w przypadku błędów związanych z funkcją
eval(), choć we współczesnym JavaScripcie rzadko się pojawiają.Możemy tworzyć i wyrzucać własne błędy, aby obsługiwać specyficzne dla naszej aplikacji przypadki.
1// Przykład własnego błędu
2class DinosaurEscapeError extends Error {
3 constructor(dinoId, paddockId, message) {
4 super(message || `Dinozaur #${dinoId} uciekł z wybiegu #${paddockId}!`);
5 this.name = "DinosaurEscapeError";
6 this.dinoId = dinoId;
7 this.paddockId = paddockId;
8 this.timestamp = new Date();
9 }
10}
11
12// Użycie
13function sprawdźStatusWybiegu(wybiegId) {
14 const status = pobierzStatusWybiegu(wybiegId);
15
16 if (status.dinozaur && !status.bramyZamknięte) {
17 throw new DinosaurEscapeError(
18 status.dinozaur.id,
19 wybiegId,
20 `ALERT! Dinozaur ${status.dinozaur.name} uciekł z wybiegu #${wybiegId}!`
21 );
22 }
23
24 return status;
25}Podstawowy mechanizm obsługi błędów w JavaScripcie to blok
try...catch:1try {
2 // Kod, który może spowodować błąd
3 const statusWybiegu = sprawdźStatusWybiegu(12);
4 aktualizujPanelMonitoringu(statusWybiegu);
5} catch (error) {
6 // Kod obsługi błędu
7 console.error("Błąd sprawdzania wybiegu:", error.message);
8 aktywujAlarm("Błąd monitoringu wybiegu", error);
9} finally {
10 // Ten kod jest wykonywany zawsze, niezależnie od tego, czy wystąpił błąd
11 zapiszLogSystemowy("Zakończono sprawdzanie wybiegu 12");
12}W powyższym przykładzie:
try zawiera kod, który może spowodować błądcatch jest wykonywany, gdy wystąpi błąd w bloku tryfinally jest wykonywany zawsze, niezależnie od tego, czy wystąpił błąd czy nieObiekt błędu, który jest przekazywany do bloku
catch, zawiera wiele przydatnych właściwości:1try {
2 throw new Error("Coś poszło nie tak");
3} catch (error) {
4 console.error("Nazwa błędu:", error.name); // "Error"
5 console.error("Wiadomość:", error.message); // "Coś poszło nie tak"
6 console.error("Stos wywołań:", error.stack); // Stos wywołań pokazujący, gdzie błąd wystąpił
7}Często chcemy różnie reagować na różne typy błędów:
1try {
2 const wynikOperacji = przeprowadźRyzykownąOperację();
3 wyświetlWynik(wynikOperacji);
4} catch (error) {
5 if (error instanceof SyntaxError) {
6 console.error("Błąd składni:", error.message);
7 } else if (error instanceof ReferenceError) {
8 console.error("Błąd referencji:", error.message);
9 } else if (error instanceof DinosaurEscapeError) {
10 // Uruchomienie procedury awaryjnej!
11 aktywujProtokółBezpieczeństwa(error.dinoId, error.paddockId);
12 ewakuujOdwiedzających();
13 powiadomZespółBezpieczeństwa(error);
14 } else {
15 console.error("Wystąpił nieznany błąd:", error);
16 }
17}Możemy sami wyrzucać błędy za pomocą słowa kluczowego
throw:1function ustawTemperaturęWybiegu(wybiegId, temperatura) {
2 // Sprawdzanie argumentów
3 if (typeof wybiegId !== 'number' || wybiegId <= 0) {
4 throw new TypeError("ID wybiegu musi być liczbą dodatnią");
5 }
6
7 if (temperatura < 10 || temperatura > 40) {
8 throw new RangeError("Temperatura musi być między 10°C a 40°C");
9 }
10
11 // Reszta kodu, który ustawi temperaturę
12 return aktualizujTemperaturęWybiegu(wybiegId, temperatura);
13}
14
15// Użycie
16try {
17 ustawTemperaturęWybiegu("nieprawidłowe-id", 25);
18} catch (error) {
19 if (error instanceof TypeError) {
20 console.error("Nieprawidłowy typ argumentu:", error.message);
21 // Logika obsługi błędu typu
22 } else if (error instanceof RangeError) {
23 console.error("Wartość poza zakresem:", error.message);
24 // Logika obsługi błędu zakresu
25 } else {
26 console.error("Nieoczekiwany błąd:", error.message);
27 // Ogólna obsługa błędu
28 }
29}Dla bardziej zaawansowanej obsługi błędów, możemy tworzyć własne klasy błędów dziedziczące po
Error:1// Hierarchia błędów dla systemów parku
2class ParkError extends Error {
3 constructor(message) {
4 super(message);
5 this.name = this.constructor.name;
6 // Zachowanie stack trace
7 if (Error.captureStackTrace) {
8 Error.captureStackTrace(this, this.constructor);
9 }
10 }
11}
12
13class SecuritySystemError extends ParkError {
14 constructor(message, systemId) {
15 super(message || `Błąd systemu bezpieczeństwa #${systemId}`);
16 this.systemId = systemId;
17 }
18}
19
20class FeedingSystemError extends ParkError {
21 constructor(message, paddockId, foodType) {
22 super(message || `Błąd systemu karmienia w wybiegu #${paddockId}`);
23 this.paddockId = paddockId;
24 this.foodType = foodType;
25 }
26}
27
28class VisitorSystemError extends ParkError {
29 constructor(message, areaId, visitorCount) {
30 super(message || `Błąd systemu obsługi odwiedzających w strefie #${areaId}`);
31 this.areaId = areaId;
32 this.visitorCount = visitorCount;
33 }
34}
35
36// Użycie
37function uruchomSystemKarmienia(wybiegId, typPokarmu) {
38 try {
39 // Próba uruchomienia systemu karmienia
40 if (!czyWybiegIstnieje(wybiegId)) {
41 throw new FeedingSystemError(
42 `Nie można uruchomić systemu karmienia: Wybieg #${wybiegId} nie istnieje`,
43 wybiegId,
44 typPokarmu
45 );
46 }
47
48 // Więcej kodu...
49
50 } catch (error) {
51 if (error instanceof FeedingSystemError) {
52 // Specyficzna obsługa błędu systemu karmienia
53 powiadomPersonelKarmienia(error);
54 return { success: false, error: error.message, systemType: "feeding" };
55 } else {
56 // Ogólna obsługa błędu
57 console.error("Nieoczekiwany błąd:", error);
58 return { success: false, error: "Nieznany błąd systemu", systemType: "unknown" };
59 }
60 }
61}Obsługa błędów w kodzie asynchronicznym może być bardziej skomplikowana, szczególnie gdy mamy do czynienia z callbackami, Promise'ami i funkcjami async/await.
W starszych stylach programowania asynchronicznego, z callbackami, często stosowano wzorzec "error-first":
1// Styl error-first
2function pobierzDaneDinozaura(id, callback) {
3 // Wykonanie asynchronicznej operacji
4 setTimeout(() => {
5 if (id <= 0) {
6 // Pierwszy argument to błąd (jeśli wystąpił)
7 callback(new Error(`Nieprawidłowe ID dinozaura: ${id}`), null);
8 } else {
9 // Pierwszy argument to null (brak błędu), drugi to wynik
10 callback(null, { id, name: `Dino-${id}`, species: "Tyrannosaurus" });
11 }
12 }, 1000);
13}
14
15// Użycie
16pobierzDaneDinozaura(0, (error, dino) => {
17 if (error) {
18 console.error("Nie udało się pobrać danych dinozaura:", error.message);
19 return;
20 }
21
22 console.log("Pobrano dane dinozaura:", dino);
23});W Promise'ach używamy metody
.catch() do obsługi błędów:1function pobierzDaneDinozaura(id) {
2 return new Promise((resolve, reject) => {
3 // Wykonanie asynchronicznej operacji
4 setTimeout(() => {
5 if (id <= 0) {
6 reject(new Error(`Nieprawidłowe ID dinozaura: ${id}`));
7 } else {
8 resolve({ id, name: `Dino-${id}`, species: "Tyrannosaurus" });
9 }
10 }, 1000);
11 });
12}
13
14// Użycie
15pobierzDaneDinozaura(0)
16 .then(dino => {
17 console.log("Pobrano dane dinozaura:", dino);
18 })
19 .catch(error => {
20 console.error("Nie udało się pobrać danych dinozaura:", error.message);
21 });Promise'y pozwalają na łańcuchową obsługę błędów:
1pobierzDaneDinozaura(42)
2 .then(dino => {
3 return aktualizujStatusDinozaura(dino.id, "active");
4 })
5 .then(status => {
6 console.log("Status zaktualizowany:", status);
7 return wyslijPowiadomienie("Dinozaur aktywowany");
8 })
9 .catch(error => {
10 // Ten catch obsłuży błędy z wszystkich poprzednich operacji
11 console.error("Wystąpił błąd w łańcuchu operacji:", error.message);
12 });Funkcje async/await pozwalają na używanie tradycyjnej składni
try...catch nawet dla kodu asynchronicznego:1async function aktywujDinozaura(id) {
2 try {
3 // Próba aktywacji dinozaura
4 console.log(`Rozpoczynam procedurę aktywacji dinozaura #${id}...`);
5
6 const dino = await pobierzDaneDinozaura(id);
7 console.log(`Pobrano dane dinozaura: ${dino.name}`);
8
9 const statusWybiegu = await sprawdźStatusWybiegu(dino.paddockId);
10 console.log(`Status wybiegu #${dino.paddockId}: ${statusWybiegu.status}`);
11
12 if (!statusWybiegu.isSecure) {
13 throw new SecuritySystemError(
14 `Nie można aktywować dinozaura: Wybieg #${dino.paddockId} nie jest bezpieczny`,
15 "paddock-security"
16 );
17 }
18
19 const wynik = await aktualizujStatusDinozaura(id, "active");
20 console.log(`Status dinozaura zaktualizowany: ${wynik.status}`);
21
22 return {
23 success: true,
24 message: `Dinozaur #${id} został pomyślnie aktywowany`,
25 dino
26 };
27 } catch (error) {
28 console.error(`Błąd podczas aktywacji dinozaura #${id}:`, error.message);
29
30 // Możemy reagować różnie w zależności od typu błędu
31 if (error instanceof SecuritySystemError) {
32 await aktywujAlarmBezpieczeństwa(error.systemId, error.message);
33 }
34
35 return {
36 success: false,
37 message: `Nie udało się aktywować dinozaura #${id}`,
38 error: error.message
39 };
40 }
41}Ta składnia jest znacznie czytelniejsza niż łańcuchy
.then().catch() i pozwala na bardziej naturalne podejście do obsługi błędów.Gdy używamy
Promise.all() z async/await, możemy łatwo obsługiwać błędy dla wielu równoległych operacji:1async function aktywujWszystkieDinozauryWSektorze(sektorId) {
2 try {
3 // Pobierz listę dinozaurów w sektorze
4 const dinozaury = await pobierzDinozauryWSektorze(sektorId);
5 console.log(`Znaleziono ${dinozaury.length} dinozaurów w sektorze #${sektorId}`);
6
7 // Uruchom procedurę aktywacji dla wszystkich dinozaurów równolegle
8 const wyniki = await Promise.all(
9 dinozaury.map(dino => aktywujDinozaura(dino.id))
10 );
11
12 // Sprawdź wyniki
13 const udane = wyniki.filter(w => w.success);
14 const nieudane = wyniki.filter(w => !w.success);
15
16 console.log(`Aktywowano ${udane.length} z ${dinozaury.length} dinozaurów`);
17
18 return {
19 success: nieudane.length === 0,
20 activated: udane.length,
21 failed: nieudane.length,
22 results: wyniki
23 };
24 } catch (error) {
25 console.error(`Błąd krytyczny podczas aktywacji dinozaurów w sektorze #${sektorId}:`, error);
26 return {
27 success: false,
28 message: "Nie udało się przetworzyć aktywacji dinozaurów",
29 error: error.message
30 };
31 }
32}Często w operacjach sieciowych lub komunikacji z zewnętrznymi systemami warto ponowić próbę, gdy wystąpi tymczasowy błąd:
1async function zRetryPattern(operacja, options = {}) {
2 const {
3 maxPrób = 3,
4 opóźnienie = 1000,
5 mnożnikOpóźnienia = 2,
6 logowanie = true
7 } = options;
8
9 let ostatniError;
10
11 for (let próba = 1; próba <= maxPrób; próba++) {
12 try {
13 if (logowanie && próba > 1) {
14 console.log(`Próba ${próba}/${maxPrób}...`);
15 }
16
17 return await operacja(próba);
18 } catch (error) {
19 ostatniError = error;
20
21 if (logowanie) {
22 console.warn(`Próba ${próba}/${maxPrób} nie powiodła się:`, error.message);
23 }
24
25 if (próba < maxPrób) {
26 const czasOczekiwania = opóźnienie * Math.pow(mnożnikOpóźnienia, próba - 1);
27
28 if (logowanie) {
29 console.log(`Czekam ${czasOczekiwania}ms przed kolejną próbą...`);
30 }
31
32 await new Promise(resolve => setTimeout(resolve, czasOczekiwania));
33 }
34 }
35 }
36
37 throw ostatniError;
38}
39
40// Przykład użycia
41async function pobierzDaneZWęzłaCzujnikowego(nodeId) {
42 return zRetryPattern(
43 async (próba) => {
44 console.log(`Próba ${próba}: Pobieranie danych z węzła #${nodeId}...`);
45 const response = await fetch(`https://sensor-api.jurassic-park.com/node/${nodeId}`);
46
47 if (!response.ok) {
48 throw new Error(`Odpowiedź HTTP: ${response.status}`);
49 }
50
51 return await response.json();
52 },
53 {
54 maxPrób: 5,
55 opóźnienie: 2000,
56 mnożnikOpóźnienia: 1.5
57 }
58 );
59}Ten wzorzec zapobiega powtarzającym się próbom operacji, które stale kończą się niepowodzeniem:
1class CircuitBreaker {
2 constructor(options = {}) {
3 this.failureThreshold = options.failureThreshold || 3;
4 this.resetTimeout = options.resetTimeout || 30000;
5 this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
6 this.failureCount = 0;
7 this.nextAttempt = Date.now();
8 this.onStateChange = options.onStateChange || (() => {});
9 }
10
11 async wykonajFunkcję(fn) {
12 if (this.state === 'OPEN') {
13 if (Date.now() > this.nextAttempt) {
14 this.zmieńStan('HALF_OPEN');
15 } else {
16 throw new Error('Circuit breaker is OPEN');
17 }
18 }
19
20 try {
21 const result = await fn();
22 this.sukces();
23 return result;
24 } catch (error) {
25 this.błąd(error);
26 throw error;
27 }
28 }
29
30 sukces() {
31 this.failureCount = 0;
32 if (this.state === 'HALF_OPEN') {
33 this.zmieńStan('CLOSED');
34 }
35 }
36
37 błąd(error) {
38 this.failureCount += 1;
39 if (this.state === 'HALF_OPEN' || this.failureCount >= this.failureThreshold) {
40 this.zmieńStan('OPEN');
41 }
42 }
43
44 zmieńStan(stan) {
45 this.state = stan;
46 this.onStateChange(stan);
47 if (stan === 'OPEN') {
48 this.nextAttempt = Date.now() + this.resetTimeout;
49 }
50 }
51}
52
53// Przykład użycia
54const breaker = new CircuitBreaker({
55 failureThreshold: 3,
56 resetTimeout: 10000,
57 onStateChange: (newState) => {
58 console.log(`Circuit breaker zmienił stan na: ${newState}`);
59 if (newState === 'OPEN') {
60 // Powiadom zespół o problemie z systemem
61 wyslijAlertSystemowy("System czujników jest niedostępny");
62 }
63 }
64});
65
66async function pobierzDaneZCzujnika(czujnikId) {
67 try {
68 return await breaker.wykonajFunkcję(async () => {
69 const response = await fetch(`https://sensor-api.jurassic-park.com/sensor/${czujnikId}`);
70 if (!response.ok) {
71 throw new Error(`HTTP Error: ${response.status}`);
72 }
73 return response.json();
74 });
75 } catch (error) {
76 if (error.message === 'Circuit breaker is OPEN') {
77 console.log(`Czujnik #${czujnikId} jest niedostępny. Używam danych z cache.`);
78 return pobierzDaneZCache(czujnikId);
79 }
80 throw error;
81 }
82}Ten wzorzec zapewnia alternatywne źródło danych lub funkcjonalność, gdy główne źródło zawiedzie:
1async function pobierzDaneDinozauraZFallbackiem(dinoId) {
2 try {
3 // Próba pobrania danych z głównej bazy danych
4 return await pobierzDaneDinozauraZGłównejBazy(dinoId);
5 } catch (error) {
6 console.warn(`Nie udało się pobrać danych z głównej bazy: ${error.message}`);
7
8 try {
9 // Próba pobrania danych z bazy zapasowej
10 console.log(`Próbuję pobrać dane z bazy zapasowej...`);
11 return await pobierzDaneDinozauraZBazyZapasowej(dinoId);
12 } catch (fallbackError) {
13 console.warn(`Nie udało się pobrać danych z bazy zapasowej: ${fallbackError.message}`);
14
15 try {
16 // Próba pobrania danych z lokalnego cache
17 console.log(`Próbuję pobrać dane z cache...`);
18 return await pobierzDaneDinozauraZCache(dinoId);
19 } catch (cacheError) {
20 console.error(`Wszystkie źródła danych zawiodły!`);
21
22 // Zwrócenie podstawowych danych, aby aplikacja mogła działać
23 return {
24 id: dinoId,
25 name: `Unknown Dinosaur #${dinoId}`,
26 species: "Unknown",
27 warning: "Dane niepełne - wszystkie źródła danych zawiodły"
28 };
29 }
30 }
31 }
32}Ten wzorzec izoluje zasoby do dedykowanych pul, aby awaria jednej części systemu nie wpływała na inne części:
1class Bulkhead {
2 constructor(options = {}) {
3 this.maxConcurrent = options.maxConcurrent || 10;
4 this.maxQueued = options.maxQueued || 20;
5 this.active = 0;
6 this.queue = [];
7 }
8
9 async wykonaj(fn) {
10 if (this.active >= this.maxConcurrent) {
11 if (this.queue.length >= this.maxQueued) {
12 throw new Error('Bulkhead overflow: Too many requests queued');
13 }
14
15 // Dodaj do kolejki
16 return new Promise((resolve, reject) => {
17 this.queue.push({
18 fn,
19 resolve,
20 reject
21 });
22 });
23 }
24
25 this.active += 1;
26
27 try {
28 const result = await fn();
29 return result;
30 } catch (error) {
31 throw error;
32 } finally {
33 this.active -= 1;
34
35 // Obsługuj zadania z kolejki, jeśli są dostępne
36 if (this.queue.length > 0) {
37 const request = this.queue.shift();
38 this.wykonaj(request.fn).then(request.resolve).catch(request.reject);
39 }
40 }
41 }
42}
43
44// Tworzenie oddzielnych bulkheadów dla różnych podsystemów
45const securityBulkhead = new Bulkhead({ maxConcurrent: 5, maxQueued: 10 });
46const feedingBulkhead = new Bulkhead({ maxConcurrent: 3, maxQueued: 5 });
47const monitoringBulkhead = new Bulkhead({ maxConcurrent: 10, maxQueued: 30 });
48
49// Użycie
50async function monitorujWybieg(paddockId) {
51 return monitoringBulkhead.wykonaj(async () => {
52 // Operacje monitoringu
53 return await pobierzDaneMonitoringu(paddockId);
54 });
55}
56
57async function aktualizujZabezpieczenia(paddockId, settings) {
58 return securityBulkhead.wykonaj(async () => {
59 // Operacje związane z bezpieczeństwem
60 return await aktualizujUstawieniaBezpieczeństwa(paddockId, settings);
61 });
62}W przypadku aplikacji Express/Node.js, możesz zaimplementować middleware do centralnego zarządzania błędami:
1// Centralne middleware do obsługi błędów
2function errorHandler(err, req, res, next) {
3 // Logowanie błędu
4 console.error("Wystąpił błąd:", err);
5
6 // Sprawdzanie typu błędu
7 if (err instanceof SecuritySystemError) {
8 // Specjalne traktowanie błędów bezpieczeństwa
9 aktywujProtokółBezpieczeństwa(err);
10 return res.status(500).json({
11 error: "Błąd systemu bezpieczeństwa",
12 code: "SECURITY_FAILURE",
13 message: err.message
14 });
15 } else if (err.name === "ValidationError") {
16 // Błędy walidacji
17 return res.status(400).json({
18 error: "Błąd walidacji danych",
19 details: err.details,
20 message: err.message
21 });
22 } else if (err.name === "UnauthorizedError") {
23 // Błędy autoryzacji
24 return res.status(401).json({
25 error: "Brak autoryzacji",
26 message: "Nie masz uprawnień do wykonania tej operacji"
27 });
28 }
29
30 // Domyślna obsługa błędu
31 res.status(500).json({
32 error: "Wystąpił błąd wewnętrzny serwera",
33 message: process.env.NODE_ENV === "production" ?
34 "Przepraszamy, coś poszło nie tak" : err.message
35 });
36}
37
38// Użycie w Express
39app.use(errorHandler);Podczas komunikacji z zewnętrznymi API, mogą wystąpić różne błędy sieciowe:
1async function pobierzDanePogodowe() {
2 try {
3 const response = await fetch('https://api.weather.jurassic-park.com/forecast');
4
5 if (!response.ok) {
6 // Obsługa błędów HTTP
7 if (response.status === 404) {
8 throw new Error('Endpoint API nie istnieje');
9 } else if (response.status === 401 || response.status === 403) {
10 throw new Error('Brak autoryzacji do API pogodowego');
11 } else if (response.status >= 500) {
12 throw new Error(`Błąd serwera API: ${response.status}`);
13 } else {
14 throw new Error(`Nieoczekiwany błąd HTTP: ${response.status}`);
15 }
16 }
17
18 return await response.json();
19 } catch (error) {
20 // Obsługa błędów sieciowych (np. offline, timeout)
21 if (error.name === 'TypeError' && error.message.includes('fetch')) {
22 console.error('Błąd sieci:', error);
23
24 // Spróbuj użyć danych z cache
25 const cachedData = await pobierzDaneZCache('weather-forecast');
26 if (cachedData) {
27 console.log('Używam danych pogodowych z cache');
28 return {
29 ...cachedData,
30 fromCache: true,
31 cachedAt: cachedData.timestamp
32 };
33 }
34
35 // Jeśli nie ma cache, zwróć podstawowe dane
36 return {
37 temperature: 25,
38 conditions: "Unknown",
39 warning: "Dane awaryjne - brak połączenia z API",
40 fromFallback: true
41 };
42 }
43
44 // Przekazanie innych błędów
45 throw error;
46 }
47}1async function zapiszRaportDzienny(dane) {
2 const nazwaPliku = `raport-2026-07-06.json`;
3 const ścieżka = `/var/logs/jurassic-park/${nazwaPliku}`;
4
5 try {
6 // Sprawdź, czy katalog istnieje
7 try {
8 await fs.access('/var/logs/jurassic-park');
9 } catch (dirError) {
10 console.log('Katalog nie istnieje, tworzę...');
11 await fs.mkdir('/var/logs/jurassic-park', { recursive: true });
12 }
13
14 // Zapisz plik
15 await fs.writeFile(ścieżka, JSON.stringify(dane, null, 2));
16 console.log(`Raport zapisany: ${ścieżka}`);
17
18 return { success: true, path: ścieżka };
19 } catch (error) {
20 console.error(`Błąd zapisywania raportu:`, error);
21
22 // Próba zapisu w lokalizacji alternatywnej
23 try {
24 const alternatywnŚcieżka = `./backup-logs/${nazwaPliku}`;
25 await fs.writeFile(alternatywnŚcieżka, JSON.stringify(dane, null, 2));
26 console.log(`Raport zapisany w lokalizacji alternatywnej: ${alternatywnŚcieżka}`);
27
28 return {
29 success: true,
30 path: alternatywnŚcieżka,
31 warning: "Użyto lokalizacji alternatywnej"
32 };
33 } catch (backupError) {
34 console.error(`Nie udało się zapisać raportu nawet w lokalizacji alternatywnej:`, backupError);
35
36 // Zapisz dane w pamięci aplikacji do późniejszego odzyskania
37 globalThis.pendingReports = globalThis.pendingReports || [];
38 globalThis.pendingReports.push({
39 data: dane,
40 timestamp: new Date(),
41 filename: nazwaPliku
42 });
43
44 return {
45 success: false,
46 error: error.message,
47 recoveryPlan: "Dane zostały zapisane w pamięci aplikacji do późniejszego odzyskania"
48 };
49 }
50 }
51}1async function aktualizujStatusDinozaura(dinoId, nowyStatus) {
2 // Otwórz transakcję bazodanową
3 const transakcja = await db.beginTransaction();
4
5 try {
6 // Pobierz aktualny status
7 const dinozaur = await db.query(
8 'SELECT * FROM dinozaury WHERE id = ? FOR UPDATE',
9 [dinoId],
10 { transaction: transakcja }
11 );
12
13 if (!dinozaur || dinozaur.length === 0) {
14 throw new Error(`Dinozaur o ID ${dinoId} nie istnieje`);
15 }
16
17 // Zapisz historię zmian statusu
18 await db.query(
19 'INSERT INTO historia_statusów (dinozaur_id, stary_status, nowy_status, czas) VALUES (?, ?, ?, NOW())',
20 [dinoId, dinozaur[0].status, nowyStatus],
21 { transaction: transakcja }
22 );
23
24 // Aktualizuj status
25 await db.query(
26 'UPDATE dinozaury SET status = ?, aktualizacja = NOW() WHERE id = ?',
27 [nowyStatus, dinoId],
28 { transaction: transakcja }
29 );
30
31 // Zatwierdź transakcję
32 await transakcja.commit();
33
34 console.log(`Status dinozaura #${dinoId} zaktualizowany: ${dinozaur[0].status} -> ${nowyStatus}`);
35 return { success: true, id: dinoId, previousStatus: dinozaur[0].status, newStatus: nowyStatus };
36 } catch (error) {
37 // Coś poszło nie tak, wycofaj transakcję
38 console.error(`Błąd aktualizacji statusu dinozaura #${dinoId}:`, error);
39 await transakcja.rollback();
40
41 // Obsłuż różne typy błędów bazodanowych
42 if (error.code === 'ER_DUP_ENTRY') {
43 throw new Error('Próba zduplikowanego wpisu w historii statusów');
44 } else if (error.code === 'ER_NO_REFERENCED_ROW') {
45 throw new Error(`Referencja do nieistniejącego rekordu. Dinozaur #${dinoId} mógł zostać usunięty`);
46 } else {
47 throw new Error(`Błąd bazodanowy: ${error.message}`);
48 }
49 }
50}Zawsze obsługuj błędy: Każda funkcja asynchroniczna powinna mieć jakąś formę obsługi błędów.
Używaj specyficznych typów błędów: Twórz dedykowane klasy błędów dla różnych typów problemów.
Dostarczaj użyteczne komunikaty: Błąd powinien zawierać informacje przydatne do diagnozy problemu.
Loguj błędy centralnie: Użyj systemów logowania, które pozwalają na centralne zbieranie i analizowanie błędów.
Nigdy nie ignoruj błędów: Jeśli łapiesz błąd i nie potrzebujesz go dalej obsługiwać, przynajmniej go zaloguj.
Zachowaj stos wywołań: Przy rzucaniu nowych błędów, zachowuj oryginalne stosy wywołań.
1try {
2 await ryzykownaOperacja();
3} catch (error) {
4 // Zachowanie oryginalnego stosu wywołań
5 throw new Error(`Nie udało się wykonać operacji: ${error.message}`, { cause: error });
6}Obsługuj błędy na odpowiednim poziomie: Nie wszystkie błędy powinny być obsługiwane w tym samym miejscu.
Korzystaj z try...finally: Używaj bloku
finally do czyszczenia zasobów niezależnie od tego, czy wystąpił błąd.Nie przeciążaj informacjami: Nie pokazuj użytkownikom szczegółowych informacji o błędach, które ich nie dotyczą.
Przygotuj się na nieoczekiwane: Zawsze miej plan awaryjny na wypadek nieoczekiwanych błędów.
Odpowiednia obsługa błędów jest kluczowym elementem tworzenia niezawodnych aplikacji. W kontekście Parku Jurajskiego, gdzie stawką może być bezpieczeństwo odwiedzających i cennych okazów, jest to jeszcze ważniejsze.
Dobra obsługa błędów:
Pamiętaj, że w świecie oprogramowania, podobnie jak w Parku Jurajskim, zawsze lepiej jest "spodziewać się niespodziewanego" i być na to przygotowanym.