Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Obsługa błędów w JavaScript

W Parku Jurajskim nawet najlepiej zaplanowane systemy mogą zawieść - dinozaur może wyrwać się z wybiegu, czujnik może przestać działać, a karmnik automatyczny może otrzymać nieprawidłowe dane. Właśnie dlatego każdy system bezpieczeństwa parku potrzebuje protokołów awaryjnych. W JavaScript takim protokołem jest mechanizm try/catch/finally.

Blok try/catch - protokół bezpieczeństwa

Wyobraź sobie, że każda operacja w parku to potencjalne zagrożenie. Blok

try
to strefa, w której wykonujemy ryzykowne operacje, a
catch
to ekipa ratunkowa, która wkracza, gdy coś pójdzie nie tak.

1try {
2  // Strefa ryzykowna - próba otwarcia wybiegu
3  const dinoData = JSON.parse('{ niepoprawny JSON }');
4  console.log(dinoData.species);
5} catch (error) {
6  // Ekipa ratunkowa - obsługa błędu
7  console.log("ALARM! Błąd w systemie:", error.message);
8}
9console.log("System działa dalej - park nie został zamknięty!");

Bez

try/catch
nasz program zatrzymałby się całkowicie na błędzie - jakby cały park musiał zostać ewakuowany z powodu jednego wadliwego czujnika. Dzięki obsłudze błędów, program kontynuuje działanie po obsłużeniu problemu.

Blok finally - protokół końcowy

finally
to blok, który zawsze się wykonuje - niezależnie od tego, czy błąd wystąpił, czy nie. To jak procedura zamknięcia ogrodzenia, która musi się odbyć niezależnie od wyniku misji.

1function feedDinosaur(dinoName, food) {
2  console.log("Otwieranie karmnika dla " + dinoName + "...");
3
4  try {
5    if (!food) {
6      throw new Error("Brak pożywienia w systemie!");
7    }
8    console.log("Podaję " + food + " dla " + dinoName);
9  } catch (error) {
10    console.log("ALARM: " + error.message);
11  } finally {
12    // ZAWSZE zamykamy karmnik - czy udało się nakarmić, czy nie
13    console.log("Zamykanie karmnika - bezpieczeństwo przede wszystkim!");
14  }
15}
16
17feedDinosaur("Rex", "mięso");    // Sukces + zamknięcie
18feedDinosaur("Rex", null);       // Błąd + zamknięcie (finally zawsze działa!)

Klasy błędów w JavaScript

JavaScript ma wbudowaną hierarchię błędów, podobnie jak park ma różne poziomy alertów dla różnych zagrożeń:

Główne typy błędów

  • Error - ogólny błąd (alert ogólny w parku)
  • TypeError - operacja na niewłaściwym typie danych (karmienie roślinożercy mięsem)
  • RangeError - wartość poza dozwolonym zakresem (temperatura inkubatora poza normą)
  • ReferenceError - odwołanie do nieistniejącej zmiennej (dinozaur, którego nie ma w bazie)
  • SyntaxError - błąd składni kodu (uszkodzony plan DNA)
1// TypeError - operacja na niewłaściwym typie
2try {
3  const dino = null;
4  dino.roar(); // null nie ma metod!
5} catch (error) {
6  console.log(error instanceof TypeError); // true
7  console.log(error.message); // "Cannot read properties of null"
8}
9
10// RangeError - wartość poza zakresem
11try {
12  const dinoArray = new Array(-5); // Ujemna długość tablicy!
13} catch (error) {
14  console.log(error instanceof RangeError); // true
15}
16
17// ReferenceError - nieistniejąca zmienna
18try {
19  console.log(nieistniejacyDinozaur);
20} catch (error) {
21  console.log(error instanceof ReferenceError); // true
22}

Throw - aktywacja alarmu

Słowo kluczowe

throw
pozwala nam celowo wywołać błąd - jak ręczne włączenie alarmu w parku, gdy zauważymy problem, zanim automatyczne systemy go wykryją.

1function checkEnclosureFence(voltage) {
2  if (voltage < 0) {
3    throw new RangeError("Napięcie nie może być ujemne: " + voltage);
4  }
5  if (voltage < 5000) {
6    throw new Error("ALARM: Napięcie ogrodzenia poniżej minimum! (" + voltage + "V)");
7  }
8  console.log("Ogrodzenie sprawne: " + voltage + "V");
9}
10
11try {
12  checkEnclosureFence(10000); // OK
13  checkEnclosureFence(3000);  // Wyrzuci Error
14} catch (error) {
15  console.log("Wykryto problem:", error.message);
16}

Re-throw - przekierowanie alarmu

Czasami

catch
łapie błąd, ale nie potrafi go obsłużyć - wtedy ponownie go wyrzuca (re-throw), żeby ktoś wyżej w łańcuchu dowodzenia mógł zareagować.

1function processEnclosureData(data) {
2  try {
3    const parsed = JSON.parse(data);
4    if (!parsed.species) {
5      throw new Error("Brak gatunku w danych wybiegu");
6    }
7    return parsed;
8  } catch (error) {
9    if (error instanceof SyntaxError) {
10      // Umiemy obsłużyć - uszkodzone dane
11      console.log("Uszkodzone dane, używam domyślnych wartości");
12      return { species: "Unknown", status: "error" };
13    }
14    // Inny błąd - przekazujemy wyżej
15    throw error;
16  }
17}

Własne klasy błędów

W Parku Jurajskim potrzebujemy specjalistycznych procedur awaryjnych dla różnych sytuacji. W JavaScript tworzymy własne klasy błędów rozszerzając klasę

Error
:

1class DinosaurEscapeError extends Error {
2  constructor(species, enclosure) {
3    super("ALARM: " + species + " uciekł z wybiegu " + enclosure + "!");
4    this.name = "DinosaurEscapeError";
5    this.species = species;
6    this.enclosure = enclosure;
7  }
8}
9
10class FeedingError extends Error {
11  constructor(dinoName, reason) {
12    super("Błąd karmienia " + dinoName + ": " + reason);
13    this.name = "FeedingError";
14    this.dinoName = dinoName;
15  }
16}
17
18class EnclosureVoltageError extends Error {
19  constructor(enclosure, voltage) {
20    super("Napięcie w wybiegu " + enclosure + ": " + voltage + "V (minimum 5000V)");
21    this.name = "EnclosureVoltageError";
22    this.enclosure = enclosure;
23    this.voltage = voltage;
24  }
25}
26
27// Użycie z selektywnym catch
28function parkSecurityCheck(enclosures) {
29  for (const enc of enclosures) {
30    try {
31      if (enc.voltage < 5000) {
32        throw new EnclosureVoltageError(enc.name, enc.voltage);
33      }
34      if (enc.dinoEscaped) {
35        throw new DinosaurEscapeError(enc.species, enc.name);
36      }
37      console.log("Wybieg " + enc.name + " - status OK");
38    } catch (error) {
39      if (error instanceof DinosaurEscapeError) {
40        console.log("CZERWONY ALERT: " + error.message);
41        console.log("Gatunek: " + error.species);
42      } else if (error instanceof EnclosureVoltageError) {
43        console.log("ŻÓŁTY ALERT: " + error.message);
44      } else {
45        throw error; // Nieznany błąd - re-throw
46      }
47    }
48  }
49}

Własne klasy błędów pozwalają na precyzyjną obsługę - różne typy zagrożeń wymagają różnych procedur, tak jak w prawdziwym parku różne alarmy uruchamiają różne protokoły.

Optional chaining (?.) i Nullish coalescing (??)

Te dwa operatory to jak inteligentne czujniki w parku - pomagają bezpiecznie poruszać się po danych, które mogą być niekompletne.

Optional chaining (?.)

Operator

?.
bezpiecznie odwołuje się do zagnieżdżonych właściwości. Jeśli jakakolwiek wartość po drodze jest
null
lub
undefined
, zamiast błędu zwraca
undefined
.

1const dinoRecord = {
2  species: "Velociraptor",
3  tracker: {
4    lastLocation: "Sektor C",
5    gps: { lat: 23.5, lng: -80.2 }
6  }
7};
8
9// Bez optional chaining - ryzyko TypeError
10// const lat = dinoRecord.medical.lastCheckup.date; // TypeError!
11
12// Z optional chaining - bezpieczny dostęp
13const lat = dinoRecord.medical?.lastCheckup?.date; // undefined (bez błędu!)
14const gpsLat = dinoRecord.tracker?.gps?.lat;       // 23.5
15
16// Działa też z metodami i indeksami
17const dinoCall = dinoRecord.sounds?.roar?.();       // undefined (metoda nie istnieje)
18const firstTag = dinoRecord.tags?.[0];              // undefined (brak tablicy)

Nullish coalescing (??)

Operator

??
zwraca wartość domyślną, gdy lewa strona to
null
lub
undefined
. W odróżnieniu od
||
, nie reaguje na
0
,
""
czy
false
.

1// Problem z || - traktuje 0 i "" jako "puste"
2const dinoWeight = 0;
3console.log(dinoWeight || "Brak danych");  // "Brak danych" (źle! 0 to prawidłowa waga)
4console.log(dinoWeight ?? "Brak danych");  // 0 (poprawnie!)
5
6// Praktyczny przykład z danymi dinozaurów
7function getDinoInfo(dino) {
8  const name = dino?.name ?? "Nieznany gatunek";
9  const weight = dino?.weight ?? "Brak danych";
10  const isHealthy = dino?.isHealthy ?? false;
11  const speed = dino?.speed ?? 0;
12
13  return name + " | Waga: " + weight + "kg | Zdrowy: " + isHealthy + " | Prędkość: " + speed;
14}
15
16console.log(getDinoInfo({ name: "Rex", weight: 8000 }));
17console.log(getDinoInfo(null)); // Bezpieczne - nie wyrzuci błędu

Kombinacja ?. i ??

1const parkData = {
2  enclosures: {
3    "A-1": { species: "T-Rex", voltage: 10000 }
4  }
5};
6
7// Bezpieczne odczytanie danych z wartością domyślną
8const voltage = parkData.enclosures?.["B-2"]?.voltage ?? "Brak czujnika";
9console.log("Napięcie B-2:", voltage); // "Brak czujnika"
10
11const rexVoltage = parkData.enclosures?.["A-1"]?.voltage ?? 0;
12console.log("Napięcie A-1:", rexVoltage); // 10000

Strategia obsługi błędów

Dobra strategia obsługi błędów w aplikacji to jak wielopoziomowy system bezpieczeństwa w Parku Jurajskim:

  1. Walidacja danych wejściowych - sprawdzaj dane ZANIM staną się problemem (jak kontrola na bramce parku)
  2. Specyficzne typy błędów - twórz własne klasy dla różnych sytuacji (różne alarmy dla różnych zagrożeń)
  3. Obsługuj na właściwym poziomie - łap błędy tam, gdzie możesz je naprawić (nie wynoś alarmów o karmniku do dyrektora parku)
  4. Re-throw nieznanych błędów - jeśli nie wiesz, jak obsłużyć błąd, przekaż go wyżej
  5. Używaj finally do czyszczenia zasobów - zamykaj połączenia, pliki, ogrodzenia - niezależnie od błędów
Przejdź do CodeWorlds