Usamos cookies para mejorar tu experiencia en el sitio
CodeWorlds

Obsługa błędów (try...catch)

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.

Dlaczego obsługa błędów jest ważna?

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:

  • Zapobiega "awariom kaskadowym" - jeden błąd nie powinien powodować awarii całego systemu
  • Dostarcza użytecznych informacji diagnostycznych - pomaga szybko zidentyfikować i naprawić problem
  • Umożliwia działania naprawcze - automatyczne próby ponowienia, przełączanie na systemy zapasowe
  • Zapewnia "graceful degradation" - nawet gdy część funkcji jest niedostępna, kluczowe systemy nadal działają

Typy błędów w JavaScript

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:

1. Błędy składniowe (SyntaxError)

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}

2. Błędy referencyjne (ReferenceError)

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}

3. Błędy typu (TypeError)

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}

4. Błędy zakresu (RangeError)

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}

5. Błędy URI (URIError)

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}

6. Błędy EvalError

Występowały historycznie w przypadku błędów związanych z funkcją

eval()
, choć we współczesnym JavaScripcie rzadko się pojawiają.

7. Błędy własne (niestandardowe)

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}

Podstawowa obsługa błędów z try...catch

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:

  • Blok
    try
    zawiera kod, który może spowodować błąd
  • Blok
    catch
    jest wykonywany, gdy wystąpi błąd w bloku
    try
  • Blok
    finally
    jest wykonywany zawsze, niezależnie od tego, czy wystąpił błąd czy nie

Właściwości obiektu Error

Obiekt 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}

Sprawdzanie typu błędu

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}

Wyrzucanie błędów (throw)

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}

Tworzenie własnych klas błędów

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

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.

Obsługa błędów w callbackach

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});

Obsługa błędów w Promise

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  });

Obsługa błędów z async/await

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.

Łączenie obsługi błędów w async/await z Promise.all()

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}

Zaawansowane strategie obsługi błędów

1. Retry Pattern (Wzorzec ponawiania prób)

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}

2. Circuit Breaker Pattern (Wzorzec wyłącznika)

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}

3. Fallback Pattern (Wzorzec alternatywny)

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}

4. Bulkhead Pattern (Wzorzec grodzi)

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}

5. Error Middleware (Middleware do obsługi błędów)

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);

Obsługa błędów w praktyce - przypadki użycia

1. Obsługa błędów sieci

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}

2. Obsługa błędów operacji na plikach

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}

3. Obsługa błędów w operacjach bazodanowych

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}

Dobre praktyki obsługi błędów

  1. Zawsze obsługuj błędy: Każda funkcja asynchroniczna powinna mieć jakąś formę obsługi błędów.

  2. Używaj specyficznych typów błędów: Twórz dedykowane klasy błędów dla różnych typów problemów.

  3. Dostarczaj użyteczne komunikaty: Błąd powinien zawierać informacje przydatne do diagnozy problemu.

  4. Loguj błędy centralnie: Użyj systemów logowania, które pozwalają na centralne zbieranie i analizowanie błędów.

  5. Nigdy nie ignoruj błędów: Jeśli łapiesz błąd i nie potrzebujesz go dalej obsługiwać, przynajmniej go zaloguj.

  6. 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}
  1. Obsługuj błędy na odpowiednim poziomie: Nie wszystkie błędy powinny być obsługiwane w tym samym miejscu.

  2. Korzystaj z try...finally: Używaj bloku

    finally
    do czyszczenia zasobów niezależnie od tego, czy wystąpił błąd.

  3. Nie przeciążaj informacjami: Nie pokazuj użytkownikom szczegółowych informacji o błędach, które ich nie dotyczą.

  4. Przygotuj się na nieoczekiwane: Zawsze miej plan awaryjny na wypadek nieoczekiwanych błędów.

Podsumowanie

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:

  • Zapewnia, że system będzie działał nawet w przypadku problemów
  • Ułatwia diagnozowanie i naprawianie problemów
  • Poprawia doświadczenie użytkownika
  • Zapobiega kaskadowym awariom

Pamiętaj, że w świecie oprogramowania, podobnie jak w Parku Jurajskim, zawsze lepiej jest "spodziewać się niespodziewanego" i być na to przygotowanym.

Ir a CodeWorlds