Paweł Łukasiewicz
2024-12-02
Paweł Łukasiewicz
2024-12-02
Udostępnij Udostępnij Kontakt
Wprowadzenie

Obsługa błędów to kluczowy element każdej aplikacji, a TypeScript dostarcza narzędzi, które pozwalają na skuteczne typowanie oraz zarządzanie wyjątkami. Dzięki statycznemu typowaniu w TypeScript możemy przewidywać i obsługiwać błędy w bardziej kontrolowany sposób niż w czystym JavaScript, co przekłada się na bardziej niezawodny kod.

W tym wpisie omówimy, jak poprawnie typować i obsługiwać wyjątki, oraz jak wykorzystywać mechanizmy wbudowane w TypeScript do zarządzania wyjątkami.

Obsługa wyjątków w TypeScript

TypeScript, podobnie jak JavaScript, używa mechanizmu try...catch do obsługi błędów w czasie wykonania. Klasyczna struktura wygląda następująco:

Przykład:

try { 
    // Kod, który może wygenerować błąd 
} catch (error) { 
    // Obsługa błędu 
} finally { 
    // Kod, który wykona się niezależnie od tego, czy błąd wystąpił 
}
Choć ten mechanizm nie różni się od standardowego podejścia w JavaScript, TypeScript daje nam dodatkową kontrolę nad typami wyjątków, co pozwala na bardziej precyzyjne zarządzanie błędami.

Typowanie błędów w bloku catch

W JavaScript błąd rzucony w bloku try może być dowolnego typu — obiektem, stringiem czy nawet liczbą. TypeScript pozwala na lepsze typowanie tych błędów, co pomaga zminimalizować ryzyko nieoczekiwanych zachowań.

Domyślnie TypeScript traktuje zmienną błędu w bloku catch jako any. Możemy jednak użyć narzędzi, aby lepiej kontrolować, jaki typ błędu chcemy obsłużyć.

Przykład z rzutowaniem błędu:

try { 
    throw new Error("Wystąpił błąd!"); 
} catch (error) { 
    const typedError = error as Error; 
    console.log(typedError.message); 
}
W powyższym przykładzie używamy as Error, aby upewnić się, że obiekt error jest instancją klasy Error. Dzięki temu możemy bezpiecznie korzystać z właściwości takich jak message.

Typowanie własnych wyjątków

W TypeScript możemy tworzyć własne klasy wyjątków, które będą dziedziczyć po klasie Error. Takie podejście jest przydatne, gdy chcemy jasno określić, jakie rodzaje błędów mogą wystąpić w naszej aplikacji.

Przykład:

class ValidationError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "ValidationError";
  }
}

try {
  throw new ValidationError("Niepoprawne dane!");
} catch (error) {
  if (error instanceof ValidationError) {
    console.log(`Błąd walidacji: ${error.message}`);
  } else {
    console.log("Inny błąd:", error);
  }
}
W tym przypadku definiujemy własny wyjątek ValidationError, który pozwala na bardziej specyficzną obsługę błędów walidacji.

Typowanie funkcji rzucających błędy

W TypeScript możemy również określać, że funkcja może rzucać wyjątki, co zwiększa przejrzystość kodu i ułatwia jego późniejsze testowanie.

Przykład:

function riskyOperation(): void {
  if (Math.random() > 0.5) {
    throw new Error("Coś poszło nie tak!");
  }
}

try {
  riskyOperation();
} catch (error) {
  console.error("Błąd:", error);
}
Choć TypeScript nie posiada wbudowanego mechanizmu do oznaczania funkcji jako rzucających wyjątki (jak np. w językach takich jak Java, gdzie można deklarować wyjątki), możemy dodawać komentarze i notacje, które pomogą programistom przewidzieć możliwe błędy.

Typy wyjątków i obsługa warunkowa

Czasami chcemy obsłużyć błędy w zależności od ich typu. Możemy to zrobić, używając operatora instanceof, aby określić, z jakim wyjątkiem mamy do czynienia.

Przykład:

class NetworkError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "NetworkError";
  }
}

class DatabaseError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "DatabaseError";
  }
}

try {
  throw new NetworkError("Sieć jest niedostępna.");
} catch (error) {
  if (error instanceof NetworkError) {
    console.log("Obsługa błędu sieci:", error.message);
  } else if (error instanceof DatabaseError) {
    console.log("Obsługa błędu bazy danych:", error.message);
  } else {
    console.log("Inny błąd:", error);
  }
}
W tym przykładzie definiujemy dwie klasy wyjątków: NetworkError i DatabaseError. Obsługujemy każdy z nich inaczej, co pozwala na bardziej elastyczne zarządzanie błędami.

Obsługa błędów asynchronicznych

W aplikacjach, które korzystają z operacji asynchronicznych, np. pracy z API czy zapytań do bazy danych, często spotykamy się z asynchroniczną obsługą błędów.

W TypeScript możemy używać mechanizmu async/await wraz z blokiem try...catch do obsługi błędów asynchronicznych:

Przykład:

async function fetchData() {
  try {
    const response = await fetch("https://api.example.com/data");
    if (!response.ok) {
      throw new Error("Nie udało się pobrać danych.");
    }
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error("Błąd podczas pobierania danych:", error);
  }
}

fetchData();
W tym przykładzie funkcja fetchData próbuje pobrać dane z zewnętrznego API. Jeżeli coś pójdzie nie tak (np. problem z siecią lub niepoprawna odpowiedź), błąd zostanie przechwycony w bloku catch.

Wyjątki a typ never

TypeScript wprowadza specjalny typ never, który oznacza, że funkcja nigdy nie zwróci wartości, ponieważ np. zawsze rzuca wyjątek lub nigdy nie kończy działania.

Funkcje, które rzucają wyjątki, często mogą mieć przypisany typ zwracany jako never

Przykład:

function throwError(message: string): never {
  throw new Error(message);
}

function infiniteLoop(): never {
  while (true) {
    // nieskończona pętla
  }
}
Funkcja throwError nigdy nie zwróci wartości, ponieważ zawsze rzuca wyjątek. TypeScript pomaga w ten sposób lepiej opisać funkcje, które mają specyficzne zachowanie.

Podsumowanie

Obsługa błędów i typowanie wyjątków w TypeScript pozwala na bardziej precyzyjne zarządzanie błędami, dzięki czemu nasz kod jest bardziej czytelny i odporny na nieoczekiwane sytuacje. Kluczowe elementy obejmują:

  • Używanie mechanizmu try...catch do obsługi błędów.
  • Typowanie błędów w bloku catch dla lepszej kontroli.
  • Tworzenie własnych klas wyjątków w celu specyficznej obsługi błędów.
  • Wykorzystanie typów never w funkcjach, które zawsze rzucają wyjątki.

Zarządzanie błędami to istotny aspekt każdej aplikacji, a TypeScript dostarcza narzędzi, które znacznie ułatwiają pisanie bezpieczniejszego kodu.