Kurs JavaScript i TypeScript · Moduł 2: Funkcje, tablice i obiekty
Obsługa błędów w JavaScript
W tej lekcji7
W Parku Jurajskim nawet najstaranniej zaplanowane systemy w końcu zawodzą - dinozaur wyłamuje się z wybiegu, czujnik przestaje raportować, automatyczny karmnik dostaje uszkodzone dane. Właśnie dlatego każdy system bezpieczeństwa parku ma swoje protokoły awaryjne: pierwsza usterka nie zamyka parku, tylko uruchamia procedurę. W JavaScript tą procedurą 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 jest strefą, w której wykonujemy ryzykowną operację, a catch ekipą ratunkową, która wkracza, gdy coś pójdzie nie tak. Podręcznikowym przykładem takiej ryzykownej operacji jest parsowanie JSON, bo transmisja z czujnika potrafi dotrzeć uszkodzona.
1try {
2 // Strefa ryzykowna - parsowanie danych z czujnika
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! Awaria systemu:", error.message);
8}
9console.log("System działa dalej - park nie został zamknięty!");Bez try/catch program zatrzymałby się na tym błędzie na dobre - tak jakby cały park trzeba było ewakuować z powodu jednego wadliwego czujnika. Tutaj błąd trafia do obiektu error, blok catch zapisuje jego komunikat w konsoli, a program kontynuuje działanie tuż za całą konstrukcją. Dlatego ostatnia linia mimo wszystko się wypisuje.
Przyjrzyj się temu, jaką drogą błąd w ogóle do nas dociera. JSON.parse nie zwraca żadnej wartości błędu, którą dałoby się porównać przez ===, więc zapis w rodzaju if (JSON.parse(data) === error) nie ma szans zadziałać. Nie ma tu też callbacku .onError(), który można by zarejestrować, a wynik parsowania nie jest obietnicą (promise) - nie doczepisz do niego .catch(), a tym bardziej nie odwrócisz tego zapisu do postaci error.catch(JSON.parse(data)). Rzucony błąd potrafi przechwycić wyłącznie blok catch. Jedyny prawidłowy sposób obsługi błędu podczas parsowania JSON wygląda więc tak: try { JSON.parse(data); } catch (error) { handleError(error); }.
Sama konstrukcja zawsze ma ten sam kształt i składa się z pięciu części, wpisywanych dokładnie w tej kolejności: słowo kluczowe try, blok w klamrach z ryzykownym kodem { riskyCode(); }, słowo kluczowe catch, parametr (error) w nawiasach, a na końcu blok obsługi { handleError(error); }. Nawias z parametrem należy do catch, nigdy do try - to najczęstsza pomyłka przy pierwszych próbach. Zapamiętaj ten pięcioelementowy szkielet raz, a potem odtworzysz go z pamięci przy każdym ryzykownym wywołaniu.
Blok finally - protokół końcowy
finally to blok, który wykonuje się zawsze - niezależnie od tego, czy błąd wystąpił, czy nie. To procedura domknięcia ogrodzenia: obowiązuje bez względu na wynik misji, a w kodzie odpowiada jej wszystko, o czym nie wolno zapomnieć nawet w panice.
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 działa zawsze!)Porównaj oba wywołania. Pierwsze karmienie kończy się sukcesem, więc catch w ogóle nie wchodzi do gry - a karmnik i tak zostaje zamknięty. Drugie rzuca błąd, catch ogłasza alarm - i karmnik znowu zostaje zamknięty. Tutaj rodzi się najczęstsze nieporozumienie: finally nie jest blokiem, który wykonuje się tylko gdy wystąpi błąd w bloku try, ani takim, który wykonuje się tylko gdy nie wystąpi żaden błąd, ani takim, który zależy od tego, czy blok catch pomyślnie obsłużył błąd. On przechodzi za każdym razem, również wtedy, gdy w try pojawi się return.
Cały cykl obsługi błędu przebiega przez pięć etapów:
- Kod w bloku
trynapotyka błąd - Wykonanie przechodzi do bloku
catchz obiektemerror - Blok
catchobsługuje błąd (logowanie, naprawa) - Blok
finallywykonuje operacje końcowe (czyszczenie zasobów) - Program kontynuuje działanie po bloku try/catch/finally
W czwartym etapie umieszczamy więc wszystko, co trzeba zwolnić bez względu na przebieg misji: połączenia, pliki i karmniki, które ktoś zostawił otwarte.
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 skalą)
- ReferenceError - odwołanie do nieistniejącej zmiennej (dinozaur, którego nie ma w bazie)
- SyntaxError - błąd składni kodu (uszkodzony plan DNA)
Każdą z tych klas rozpoznasz wewnątrz catch operatorem instanceof i to właśnie ten test decyduje, którą procedurę awaryjną uruchomić - park musi wiedzieć, jaki alarm się włączył, zanim wyśle ekipę.
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(missingDinosaur);
20} catch (error) {
21 console.log(error instanceof ReferenceError); // true
22}Trzy różne awarie, trzy różne klasy, a obsługuje je ten sam kształt catch. Właściwość message niesie opis czytelny dla człowieka, natomiast instanceof daje kategorię, na której program potrafi się rozgałęzić. Wszystkie trzy powstają w trakcie działania programu i dlatego try/catch w ogóle ma szansę je przechwycić.
SyntaxError wyłamuje się z tej reguły, bo popsutą składnię we własnym pliku silnik wychwytuje jeszcze zanim program wystartuje. W czasie działania spotkasz ją w dwóch miejscach: wewnątrz eval oraz - znacznie częściej - przy parsowaniu uszkodzonego JSON.
1// SyntaxError - uszkodzony kod, zwykle wychwycony przed startem programu
2try {
3 eval("function(");
4} catch (error) {
5 console.log(error instanceof SyntaxError); // true
6}
7
8// Ta sama klasa wraca z uszkodzonego JSON
9try {
10 JSON.parse("{ niepoprawny JSON }");
11} catch (error) {
12 console.log(error instanceof SyntaxError); // true
13}Drugi przykład domyka klamrę z naszym pierwszym fragmentem: alarm podniesiony przez JSON.parse to SyntaxError. Dzięki temu odróżnisz w kodzie transmisję, która przyszła uszkodzona, od transmisji, która dotarła w całości, ale zawiera bzdurne wartości. To dwie zupełnie różne awarie i wymagają dwóch różnych reakcji dyżurnego.
Throw - aktywacja alarmu
Słowo kluczowe throw pozwala celowo wywołać błąd - jak ręczne włączenie alarmu w parku w chwili, gdy zauważymy problem, zanim zdążą go wykryć systemy automatyczne. Towarzyszy mu jedna żelazna zasada: rzucaj obiekt Error albo jego podklasę, nigdy zwykły napis, bo tylko obiekt błędu niesie ze sobą komunikat, nazwę i ślad stosu wywołań.
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); // Rzuca Error
14} catch (error) {
15 console.log("Wykryto problem:", error.message);
16}Dwa zabezpieczenia, dwie różne klasy: ujemny odczyt jest fizycznie niemożliwy, więc dostaje RangeError, a ogrodzenie tylko zbyt słabe - zwykły Error. W chwili, gdy throw zadziała, reszta funkcji zostaje porzucona: drugie wywołanie nigdy nie wypisze komunikatu o sprawnym ogrodzeniu, sterowanie skacze prosto do catch, a ewentualna trzecia instrukcja w bloku try też się nie wykona. Funkcja niczego nie rozstrzyga - ona tylko sygnalizuje, a decyzję podejmuje wywołujący. Ten sam mechanizm wykorzystasz przy walidacji danych: gdy funkcja wyszukująca nie znajdzie dinozaura, rzucenie Error jest znacznie bezpieczniejsze niż ciche zwrócenie undefined, a przy karmieniu każda złamana reguła - nieznana dieta, niemożliwy poziom zdrowia, pusty magazyn pożywienia - podnosi własny alarm.
Re-throw - przekazanie alarmu wyżej
Bywa, że catch przechwyci błąd, z którym sam nie potrafi nic zrobić. Wtedy wyrzuca go ponownie (re-throw), żeby zareagował ktoś wyżej w łańcuchu dowodzenia. Złapanie błędu tylko po to, by po cichu zniknął, jest najgorszym z możliwych rozwiązań: alarm milknie, ale dinozaur nadal biega na wolności.
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 // Ten przypadek umiemy obsłużyć - dane są uszkodzone
11 console.log("Uszkodzone dane, używam wartości domyślnych");
12 return { species: "Unknown", status: "error" };
13 }
14 // Każdy inny błąd - przekazujemy wyżej
15 throw error;
16 }
17}Ta funkcja jest wybredna z premedytacją. Uszkodzony JSON daje SyntaxError, z którego potrafi się podnieść, podstawiając bezpieczny rekord zastępczy. Brak gatunku to zupełnie inna historia: dane sparsowały się poprawnie, więc zgadywanie zamiotłoby pod dywan prawdziwą usterkę w bazie parku - i dlatego ten błąd wędruje wyżej, do kodu, który ma dość kontekstu, by podjąć decyzję. try/catch ma filtrować błędy, a nie je pochłaniać. Połykanie wszystkiego w jednym pustym catch to przepis na usterki niewidoczne przez wiele miesięcy.
Własne klasy błędów
W Parku Jurajskim różne sytuacje wymagają wyspecjalizowanych procedur awaryjnych. W JavaScript dostajemy je, pisząc własne klasy błędów, które rozszerzają wbudowaną 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}Przepis jest za każdym razem ten sam: konstruktor przekazuje gotowy komunikat do super(...), ustawia czytelną nazwę w name, a potem zapisuje dodatkowe fakty potrzebne ekipie interwencyjnej - gatunek, numer wybiegu, zmierzone napięcie. Tą samą metodą dopiszesz SensorError dla czujnika, który przestał odpowiadać. Nic nie stoi też na przeszkodzie, żeby postawić nad nimi wspólną klasę nadrzędną, na przykład ParkError extends Error z polem code - wtedy pojedyncze sprawdzenie instanceof ParkError złapie całą rodzinę błędów parku naraz.
Gdy klasy są gotowe, obchód ochrony może reagować na każde zagrożenie inaczej:
1// Użycie z selektywnym catch
2function parkSecurityCheck(enclosures) {
3 for (const enc of enclosures) {
4 try {
5 if (enc.voltage < 5000) {
6 throw new EnclosureVoltageError(enc.name, enc.voltage);
7 }
8 if (enc.dinoEscaped) {
9 throw new DinosaurEscapeError(enc.species, enc.name);
10 }
11 console.log("Wybieg " + enc.name + " - status OK");
12 } catch (error) {
13 if (error instanceof DinosaurEscapeError) {
14 console.log("CZERWONY ALERT: " + error.message);
15 console.log("Gatunek: " + error.species);
16 } else if (error instanceof EnclosureVoltageError) {
17 console.log("ŻÓŁTY ALERT: " + error.message);
18 } else {
19 throw error; // Nieznany błąd - re-throw
20 }
21 }
22 }
23}Blok catch czytaj jak stanowisko dyspozytora: ucieczka dostaje czerwony alert razem z gatunkiem odczytanym prosto z obiektu błędu, słabe ogrodzenie żółty alert, a wszystko, czego dyspozytor nie rozpoznaje, wraca w górę zamiast być obsłużone na oślep. Ponieważ try siedzi w środku pętli, jeden felerny wybieg nie przerywa kontroli pozostałych - skaner idzie dalej. Właśnie własne klasy błędów umożliwiają taką precyzyjną obsługę: różne zagrożenia zasługują na różne procedury, dokładnie tak, jak w prawdziwym parku różne alarmy uruchamiają różne protokoły.
Optional chaining (?.) i Nullish coalescing (??)
Te dwa operatory to inteligentne czujniki parku. Pozwalają bezpiecznie poruszać się po danych, które bywają niekompletne, zamiast wysypywać centrum kontroli przy pierwszym brakującym polu.
Optional chaining (?.)
Operator ?. sięga po zagnieżdżone właściwości bez ryzyka. Jeśli którakolwiek wartość po drodze okaże się null albo undefined, całe wyrażenie zatrzymuje się w tym miejscu i oddaje wynik, zamiast wywalić program.
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)Zatrzymaj się na linii dinoRecord.medical?.lastCheckup?.date. Nasz rekord nie ma właściwości medical, a mimo to wyrażenie zwróci undefined bez wyrzucenia błędu: łańcuch urywa się na pierwszym brakującym ogniwie. Nie dostaniesz null, nie dostaniesz pustego stringa "" i nie zobaczysz TypeError, choć medical faktycznie nie istnieje - dokładnie przed tym wyjątkiem chroni cię zakomentowana linia powyżej. Zapamiętaj też dwa warianty operatora: ?.() wywoła metodę tylko wtedy, gdy ona istnieje, a ?.[0] sięgnie po indeks tylko wtedy, gdy tablica naprawdę jest na miejscu. Miej przy tym świadomość, że ?. ukrywa brak danych, ale go nie naprawia - milczący czujnik nadal milczy.
Nullish coalescing (??)
Operator ?? podstawia wartość domyślną, gdy lewa strona to null albo undefined. W odróżnieniu od || nie reaguje na 0, "" ani false - w odczytach parku to wartości najzupełniej prawidłowe.
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 dinozaur";
9 const weight = dino?.weight ?? "brak danych";
10 const isHealthy = dino?.isHealthy ?? false;
11 const speed = dino?.speed ?? 0;
12
13 return name + " | Waga [kg]: " + weight + " | Zdrowy: " + isHealthy + " | Prędkość: " + speed;
14}
15
16console.log(getDinoInfo({ name: "Rex", weight: 8000 }));
17console.log(getDinoInfo(null)); // Bezpieczne - nie wyrzuci błęduPierwsza para linii streszcza całą sprawę: świeżo wyklute pisklę dinozaura naprawdę może mieć zmierzoną wagę 0, a || wyrzuciłoby ten pomiar do kosza i wypisało "Brak danych". Popatrz również na wywołanie getDinoInfo(null) - ono się nie wywala, bo dino?.name zwiera obwód jeszcze przed odczytem właściwości. Razem ?. i ?? zamieniają awarię w sensowny raport zastępczy.
Ta sama ochrona liczy się przy konfiguracji czujników, gdzie świadomie wyłączona wartość jest pełnoprawną informacją, a nie luką do zasypania.
1const sensorConfig = {
2 voltage: 0, // celowe zero
3 active: false, // celowo wyłączony
4 label: "" // celowo pusta nazwa
5};
6
7console.log(sensorConfig.voltage ?? 10000); // 0 (jawne zero zostaje)
8console.log(sensorConfig.active ?? true); // false (jawne false zostaje)
9console.log(sensorConfig.label ?? "bez nazwy"); // "" (jawny pusty string zostaje)
10console.log(sensorConfig.missing ?? "N/A"); // "N/A" (undefined, więc wchodzi domyślna)Tylko ostatnia linia sięga po wartość domyślną, bo tylko ostatniej właściwości naprawdę brakuje. Wyłączony czujnik zostaje wyłączony, ogrodzenie odcięte od prądu na czas przeglądu zostaje na zerze, a nienazwana sonda zachowuje pustą etykietę. Dokładnie tego oczekujesz od centrum kontroli, które nie ma prawa zmyślać odczytów - gdyby w tych czterech linijkach stało ||, dyżurny zobaczyłby napięcie 10000V na ogrodzeniu, przez które spokojnie da się przejść.
Kombinacja ?. i ??
W praktyce oba operatory chodzą parami: ?. bezpiecznie prowadzi do wartości, a ?? decyduje, co pokazać, gdy ta wartość nigdy nie dotrze.
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); // 10000Wybieg B-2 nie ma jeszcze wpisu w danych, więc łańcuch daje undefined, a ?? podstawia komunikat "Brak czujnika" - tablica dyżurnego melduje lukę w pokryciu, zamiast się zawiesić. Wybieg A-1 odpowiada prawdziwym odczytem i skoro 10000 nie jest ani null, ani undefined, wartość domyślna w ogóle nie wchodzi do gry. Jedna linia zastępuje tu całą kaskadę instrukcji if, a monitoring nie przerywa pracy przez jeden brakujący rekord.
Strategia obsługi błędów
Dobra strategia obsługi błędów w aplikacji działa jak wielopoziomowy system bezpieczeństwa Parku Jurajskiego:
- Waliduj dane wejściowe - sprawdzaj je, ZANIM staną się problemem (jak kontrola na bramce parku)
- Używaj konkretnych typów błędów - twórz własne klasy dla różnych sytuacji (inny alarm dla innego zagrożenia)
- Obsługuj błędy na właściwym poziomie - łap je tam, gdzie faktycznie umiesz je naprawić (zaciętego karmnika nie zgłasza się dyrektorowi parku)
- Przekazuj wyżej nieznane błędy - jeśli nie wiesz, jak obsłużyć błąd, oddaj go w górę łańcucha
- Zwalniaj zasoby w
finally- zamykaj połączenia, pliki i ogrodzenia, cokolwiek się wydarzy
"Włączony alarm to nie katastrofa, tylko informacja" - powtarza Dr. Rex, zamykając panel kontrolny. Trzymaj się tych pięciu zasad i zapamiętaj sedno: try/catch/finally jest awaryjnym protokołem parku, własne klasy błędów są konkretnymi alarmami, które mówią ekipie, którą procedurę uruchomić, a ?. i ?? to czujniki, dzięki którym nie wszczynasz alarmu bez powodu.
Kod do tej lekcji: index.js
1// Obsluga bledow w JavaScript - Park Jurajski Edition
2console.log("=== SYSTEM BEZPIECZENSTWA PARKU JURAJSKIEGO ===\n");
3
4// ================================================
5// 1. TRY/CATCH - protokol awaryjny
6// ================================================
7console.log("--- 1. try/catch ---");
8
9try {
10 const dinoData = JSON.parse('{ "species": "T-Rex", "weight": 8500 }');
11 console.log("Dane dinozaura:", dinoData.species, "-", dinoData.weight + "kg");
12} catch (error) {
13 console.log("ALARM! Blad parsowania danych:", error.message);
14}
15
16// Blad w parsowaniu - catch lapiie blad
17try {
18 const brokenData = JSON.parse("{ zly format }");
19} catch (error) {
20 console.log("\nZlapany blad:", error.message);
21 console.log("Typ bledu:", error.constructor.name);
22}
23
24// ================================================
25// 2. FINALLY - zawsze sie wykonuje
26// ================================================
27console.log("\n--- 2. try/catch/finally ---");
28
29function feedDinosaur(dinoName, food) {
30 console.log("\nOtwieranie karmnika dla " + dinoName + "...");
31 try {
32 if (!food) {
33 throw new Error("Brak pozywienia w systemie!");
34 }
35 if (food === "trawa" && dinoName === "Rex") {
36 throw new TypeError("T-Rex nie jest roslinozerca!");
37 }
38 console.log("Podaje " + food + " dla " + dinoName + " - sukces!");
39 } catch (error) {
40 console.log("ALARM: " + error.message);
41 } finally {
42 console.log("Zamykanie karmnika - bezpieczenstwo!");
43 }
44}
45
46feedDinosaur("Rex", "mieso");
47feedDinosaur("Rex", null);
48feedDinosaur("Rex", "trawa");
49
50// ================================================
51// 3. TYPY BLEDOW
52// ================================================
53console.log("\n--- 3. Typy bledow ---");
54
55// TypeError
56try {
57 const dino = null;
58 dino.roar();
59} catch (e) {
60 console.log("TypeError:", e.message);
61}
62
63// RangeError
64try {
65 const arr = new Array(-1);
66} catch (e) {
67 console.log("RangeError:", e.message);
68}
69
70// ReferenceError
71try {
72 console.log(nieistniejacyDinozaur);
73} catch (e) {
74 console.log("ReferenceError:", e.message);
75}
76
77// ================================================
78// 4. WLASNE KLASY BLEDOW
79// ================================================
80console.log("\n--- 4. Wlasne klasy bledow ---");
81
82class DinosaurEscapeError extends Error {
83 constructor(species, enclosure) {
84 super("ALARM: " + species + " uciekl z wybiegu " + enclosure + "!");
85 this.name = "DinosaurEscapeError";
86 this.species = species;
87 this.enclosure = enclosure;
88 }
89}
90
91class EnclosureVoltageError extends Error {
92 constructor(enclosure, voltage) {
93 super("Napiecie w " + enclosure + ": " + voltage + "V (min 5000V)");
94 this.name = "EnclosureVoltageError";
95 this.enclosure = enclosure;
96 this.voltage = voltage;
97 }
98}
99
100const enclosures = [
101 { name: "A-1", species: "T-Rex", voltage: 10000, dinoEscaped: false },
102 { name: "B-3", species: "Raptor", voltage: 3000, dinoEscaped: false },
103 { name: "C-2", species: "Dilophosaurus", voltage: 8000, dinoEscaped: true },
104];
105
106for (const enc of enclosures) {
107 try {
108 if (enc.dinoEscaped) {
109 throw new DinosaurEscapeError(enc.species, enc.name);
110 }
111 if (enc.voltage < 5000) {
112 throw new EnclosureVoltageError(enc.name, enc.voltage);
113 }
114 console.log("Wybieg " + enc.name + " - OK");
115 } catch (error) {
116 if (error instanceof DinosaurEscapeError) {
117 console.log("CZERWONY ALERT: " + error.message);
118 } else if (error instanceof EnclosureVoltageError) {
119 console.log("ZOLTY ALERT: " + error.message);
120 }
121 }
122}
123
124// ================================================
125// 5. OPTIONAL CHAINING i NULLISH COALESCING
126// ================================================
127console.log("\n--- 5. ?. i ?? ---");
128
129const dinoRecord = {
130 species: "Velociraptor",
131 tracker: { lastLocation: "Sektor C", gps: { lat: 23.5 } }
132};
133
134// Optional chaining - bezpieczny dostep
135console.log("GPS:", dinoRecord.tracker?.gps?.lat); // 23.5
136console.log("Medical:", dinoRecord.medical?.lastCheckup); // undefined
137
138// Nullish coalescing - wartosc domyslna
139const weight = 0;
140console.log("|| :", weight || "Brak danych"); // "Brak danych" (zle!)
141console.log("??:", weight ?? "Brak danych"); // 0 (poprawnie!)
142
143// Kombinacja
144const voltage = dinoRecord.tracker?.voltage ?? "Brak czujnika";
145console.log("Napiecie:", voltage);Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Jaki jest prawidłowy sposób obsługi błędu, który może wystąpić podczas parsowania JSON w JavaScript?
2. Kiedy wykonuje się blok finally w konstrukcji try/catch/finally?
3. Co zwróci wyrażenie: dinoRecord.medical?.lastCheckup?.date, jeśli obiekt dinoRecord nie posiada właściwości medical?
Zadania praktyczne w grze
- Edytor kodu
Napisz funkcję findDinosaur(dinosaurs, id), która zwraca z tablicy dinozaura o podanym id, a gdy go nie ma, rzuca new Error('Nie ma dinozaura o ID ' + id). Napisz funkcję feedDinosaur(dinosaurs, id, food): w bloku try znajdź dinozaura przez findDinosaur, rzuć TypeError, gdy pokarm nie pasuje do diety (carnivore je 'meat', herbivore je 'plants'), a w przeciwnym razie zwróć true; w catch wyświetl error.message i zwróć false; w finally wyświetl 'Zamykanie karmnika'. Napisz też readVoltage(record), która bezpiecznie odczytuje record.sensors.voltage operatorem ?. i zwraca 'Brak czujnika', gdy tej wartości nie ma (użyj ??).
- Układanie w pionie
Uporządkuj etapy obsługi błędu w JavaScript od momentu wystąpienia problemu:
- Klikanie w kolejności
Ułóż elementy w prawidłowej kolejności, aby zbudować blok try/catch w JavaScript:
- Edytor kodu
Stwórz klasy DinosaurEscapeError(species, enclosure), FeedingError(dinoName, reason) i SensorError(sensorType, enclosure), które rozszerzają Error, przekazują komunikat do super(), ustawiają this.name na nazwę klasy i zapisują parametry we właściwościach o tych samych nazwach. Napisz funkcję securityScan(enclosures), która dla każdego wybiegu { name, species, voltage, dinoEscaped } w bloku try rzuca DinosaurEscapeError, gdy dinoEscaped jest true, albo SensorError('voltage', name), gdy voltage < 5000. W catch rozpoznaj błąd przez instanceof i wyświetl 'CZERWONY ALERT: ' + message albo 'ŻÓŁTY ALERT: ' + message, a błąd innego typu rzuć dalej. Funkcja zwraca obiekt z liczbą alertów { red, yellow }.