Obsługa błędów — program może zareagować

Błędny tekst nie musi kończyć całej aplikacji. Zdecyduj, gdzie zgłosić problem, gdzie go obsłużyć i kiedy prostszy będzie zwykły wynik operacji.

CEL LEKCJI

Po tej lekcji potrafisz

  • Użyjesz throw, try, catch i finally.
  • Sprawdzisz rodzaj złapanej wartości.
  • Odróżnisz oczekiwany brak wyniku od sytuacji wyjątkowej.
WPROWADZENIE

Konfiguracja nie daje się odczytać

Program otrzymał tekst, który miał opisywać ustawienia, lecz ktoś usunął cudzysłów. Funkcja odczytująca może zgłosić błąd. Kod odpowiedzialny za kontakt z użytkownikiem powinien zdecydować, jaki komunikat pokazać.

POZNAJ SKŁADNIĘ

Małe kroki, konkretne efekty

throw przerywa bieżącą drogę

throw zgłasza wyjątek i przerywa wykonywanie kolejnych instrukcji na tej drodze. Najbliższy pasujący catch może go przejąć. Rzucamy obiekt Error, żeby zachować wiadomość i informacje diagnostyczne.

src/index.ts — osobny przykład
function sprawdz(liczba: number): number {
  if (!Number.isInteger(liczba) || liczba < 1) { throw new Error("Podaj dodatnią liczbę całkowitą"); }
  return liczba;
}
try {
  console.log(sprawdz(0));
} catch (blad) {
  console.log(blad instanceof Error ? blad.message : "Nieznany błąd");
}
wynik w konsoli
Podaj dodatnią liczbę całkowitą

Zmienna catch w trybie strict ma typ unknown. JavaScript pozwala rzucić również tekst lub inny obiekt, więc nie zakładaj automatycznie istnienia message.

finally wykonuje porządkowanie

finally działa po try lub catch, także przy return. Nie umieszczaj tam return: mógłby zastąpić wcześniejszy wynik lub ukryć wyjątek. Nie jest gwarancją działania po nagłym przerwaniu całego procesu.

src/index.ts — osobny przykład
try {
  console.log("Rozpoczęto próbę");
  throw new Error("Brak biletu");
} catch (blad) {
  if (blad instanceof Error) { console.log(blad.message); }
} finally {
  console.log("Próba zakończona");
}
wynik w konsoli
Rozpoczęto próbę
Brak biletu
Próba zakończona

finally nie oznacza „udało się”. Ten sam komunikat końcowy wystąpi po sukcesie i po błędzie.

PRZYKŁAD Z OMÓWIENIEM

Czytanie ustawień gry

JSON.parse odczytuje tekst JSON i może zgłosić SyntaxError. Po odczycie sprawdzamy osobno kształt danych. Poprawna składnia nie gwarantuje poprawnego ustawienia.

src/index.ts — osobny przykład
function odczytajLimit(tekst: string): number {
  const dane: unknown = JSON.parse(tekst);
  if (typeof dane !== "object" || dane === null || !("limit" in dane)) {
    throw new Error("Brak pola limit");
  }
  if (typeof dane.limit !== "number" || !Number.isInteger(dane.limit) || dane.limit < 1 || dane.limit > 16) {
    throw new Error("Limit musi wynosić 1–16");
  }
  return dane.limit;
}
for (const tekst of ['{"limit":8}', '{"limit":99}', '{}']) {
  try { console.log("Limit: " + odczytajLimit(tekst)); }
  catch (blad) { console.log(blad instanceof Error ? blad.message : "Błąd odczytu"); }
}
wynik w konsoli
Limit: 8
Limit musi wynosić 1–16
Brak pola limit

Jak program dochodzi do wyniku

  1. Odczyt JSON daje nieznane dane, dlatego zapisujemy unknown.
  2. Pierwszy warunek sprawdza obiekt, wyklucza null i kontroluje obecność pola.
  3. Następny sprawdza rodzaj oraz zakres wartości.
  4. Każda próba ma osobny try, więc błędna konfiguracja nie zatrzymuje następnej próby.

Zanim przejdziesz dalej

Czy catch uruchomi się dla poprawnego limitu 8?

Sprawdź swoje rozumowanie

Nie. Po prawidłowym zakończeniu try program przechodzi dalej; catch służy do obsługi zgłoszonego wyjątku.

EKSPERYMENT

Zmień kod i zobacz, co się stanie

Własna klasa błędu i decyzja o obsłudze

Osobna klasa błędu pozwala odróżnić znany problem od nieoczekiwanej awarii. Obsłuż to, co rozumiesz, a nieznany wyjątek przekaż dalej.

src/index.ts — osobny przykład
class BrakMonet extends Error {}
function kup(saldo: number): void {
  if (saldo < 10) { throw new BrakMonet("Potrzebujesz 10 monet"); }
}
try { kup(5); }
catch (blad) {
  if (blad instanceof BrakMonet) { console.log(blad.message); }
  else { throw blad; }
}
wynik w konsoli
Potrzebujesz 10 monet

Brak środków można też opisać unią { ok: false, … }, szczególnie gdy to częsty, oczekiwany wynik. Wyjątki nie muszą zastępować wszystkich warunków.

CZĘSTE BŁĘDY

Gdy wynik zaskakuje

Pomyłka Co się dzieje i jak to poprawić
Pusty catch Program ukrywa przyczynę problemu. Obsłuż błąd świadomie albo przekaż dalej.
Odczyt blad.message bez sprawdzenia Złapana wartość może nie być Error.
Return w finally Może nadpisać wcześniejszy wynik i ukryć błąd.

JSON.parse jako źródło błędu

Metoda JSON.parse zamienia tekst na dane i zgłasza błąd, gdy tekst nie jest poprawnym JSON-em. Używamy jej tutaj właśnie dlatego — to najkrótszy sposób na wywołanie prawdziwego wyjątku. Samym formatem JSON i sprawdzaniem kształtu danych zajmuje się lekcja 29.

ZADANIA

Teraz Twoja kolej

Rozwiązanie jest jedną z możliwości. Zanim je odkryjesz, uruchom własną wersję dla podanych danych i przynajmniej jednego przypadku brzegowego.

ZAD 1Błąd dzielenia★☆☆

Napisz dziel, która przy b = 0 zgłasza Error z komunikatem „Nie dziel przez zero”. Sprawdź 10 / 2 i 10 / 0.

Mała podpowiedź

JavaScript sam nie rzuci wyjątku dla dzielenia number przez zero.

Przykładowe rozwiązanie z wyjaśnieniem
src/index.ts — osobny przykład
function dziel(a: number, b: number): number {
  if (b === 0) { throw new Error("Nie dziel przez zero"); }
  return a / b;
}
console.log(dziel(10, 2));
try { dziel(10, 0); } catch (e) { if (e instanceof Error) { console.log(e.message); } }
wynik w konsoli
5
Nie dziel przez zero

To reguła naszego programu, a nie automatyczne zachowanie operatora /.

ZAD 2Niepoprawny JSON★★☆

Spróbuj odczytać tekst „nie-json”. Przechwyć błąd i wypisz stały komunikat „Nie udało się odczytać”.

Mała podpowiedź

Treść komunikatu silnika może się różnić; własny komunikat pozostaje przewidywalny.

Przykładowe rozwiązanie z wyjaśnieniem
src/index.ts — osobny przykład
try { JSON.parse("nie-json"); }
catch { console.log("Nie udało się odczytać"); }
wynik w konsoli
Nie udało się odczytać

Nie potrzebujemy zmiennej błędu, gdy świadomie pokazujemy ogólny komunikat.

ZAD 3Nie zatrzymuj kolejki★★☆

Sprawdź [2, -1, 4]. Dodatnią wartość wypisz, niedodatnią zgłoś jako błąd. Trzecia próba nadal ma się wykonać.

Mała podpowiedź

try umieść wewnątrz pętli.

Przykładowe rozwiązanie z wyjaśnieniem
src/index.ts — osobny przykład
for (const n of [2, -1, 4]) {
  try {
    if (n <= 0) { throw new Error("Błędna liczba"); }
    console.log(n);
  } catch (e) { if (e instanceof Error) { console.log(e.message); } }
}
wynik w konsoli
2
Błędna liczba
4

Umiejscowienie try określa, jak duży fragment pracy przerywa wyjątek.

ZAD 4Zamknięcie próby★★★

Napisz funkcję proba, która zgłasza i obsługuje błąd „Odmowa”, a w finally wypisuje „Koniec”. Na końcu funkcja zwraca false.

Mała podpowiedź

Return pozostaw poza finally.

Przykładowe rozwiązanie z wyjaśnieniem
src/index.ts — osobny przykład
function proba(): boolean {
  try { throw new Error("Odmowa"); }
  catch (e) { if (e instanceof Error) { console.log(e.message); } }
  finally { console.log("Koniec"); }
  return false;
}
console.log(proba());
wynik w konsoli
Odmowa
Koniec
false

Porządkowanie i decyzja o wyniku są oddzielone.

PODSUMOWANIE

Co zabierasz do następnej lekcji

  • throw zgłasza wyjątek, catch go przejmuje, finally służy porządkowaniu.
  • Zmienną catch sprawdzaj jak unknown.
  • Nie każdy brak wyniku wymaga wyjątku.