Menu

Try catch w TypeScript: typy błędów i własne klasy błędów

Obsługa błędów w TypeScript: dlaczego zmienna w catch ma typ unknown, jak zawęzić ją przez instanceof Error, jak rzucać błędy, pisać własne klasy błędów z name i cause oraz stosować wzorzec typu Result dla błędów, których się spodziewasz.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

TypeScript korzysta z try, catch, finally i throw z JavaScriptu. Specyficzna dla TypeScriptu jest zmienna w catch: przy włączonym strict ma typ unknown, więc przed jej użyciem sprawdzasz, co zostało rzucone.

Część dotycząca działania programu (jak rozwija się stos, jakie są wbudowane typy błędów) jest opisana na stronie try/catch w JavaScripcie.

Dlaczego zmienna w catch ma typ unknown

JavaScript może rzucić wszystko: Error, string, liczbę, undefined, obiekt z biblioteki. TypeScript nie może wiedzieć, co dokładnie, więc przy strict (opcja useUnknownInCatchVariables) zmienna ma typ unknown i przed użyciem trzeba ją zawęzić:

index.ts(5,34): error TS18046: 'e' is of type 'unknown'.

Bez strict zmienna e ma typ any i ten sam kod się kompiluje, ale psuje się w czasie działania, gdy zostanie rzucony string: e.message jest wtedy undefined, a wszystko głębiej, na przykład e.message.length, rzuca TypeError. Adnotacja typu też tu nie pomoże: catch (e: Error) to błąd TS1196 (Catch clause variable type annotation must be 'any' or 'unknown' if specified).

Zawężanie błędu

instanceof Error obejmuje wszystko, co jest zbudowane na Error, w tym TypeError, SyntaxError, RangeError i twoje własne podklasy. W pozostałych przypadkach zamień wartość na string. Mała funkcja pomocnicza skraca kod w miejscach wywołania:

Blok catch, który obsługuje tylko niektóre błędy, powinien rzucić resztę dalej: if (!(e instanceof ValidationError)) throw e;. Połykanie nieznanych błędów ukrywa prawdziwe bugi.

Rzucanie błędów

throw przyjmuje dowolne wyrażenie i TypeScript tego nie ogranicza. Mimo to rzucaj obiekty Error: mają ślad stosu, a każde sprawdzenie instanceof Error w kodzie od tego zależy.

Funkcja, która zawsze rzuca błąd, ma typ zwracany never, a TypeScript wykorzystuje go do zawężania po jej wywołaniu:

Od ES2022 Error przyjmuje drugi argument z polem cause, które łączy błąd niskiego poziomu z tym, który rzucasz: throw new Error("could not load settings", { cause: e }). Wywołujący może odczytać err.cause (typ unknown).

Własne klasy błędów

Podklasa Error pozwala wywołującym rozróżniać porażki przez instanceof i przenosić dodatkowe dane. Ustaw name, bo odziedziczona wartość to "Error", a pojawia się ona w logach i w String(err):

Najpierw sprawdzaj najbardziej szczegółową klasę, bo NotFoundError jest też HttpError. Modyfikator override jest tu opcjonalny, chyba że włączone jest noImplicitOverride.

Starsze poradniki dodają Object.setPrototypeOf(this, new.target.prototype) do każdego konstruktora błędu. Było to potrzebne przy kompilowaniu klas do funkcji ES5, gdzie instanceof nie działał dla podklas Error. TypeScript 7 nie obsługuje już target: "es5" (zgłasza TS5108: Option 'target=ES5' has been removed), a przy ES2015 i nowszych natywne class ... extends Error działa, tak jak powyżej.

Wzorzec Result

TypeScript nie śledzi, jakie błędy może rzucić funkcja, więc nic nie przypomina wywołującym o ich obsłudze. Dla porażek, których się spodziewasz (błędne dane wejściowe, brakujący rekord), zwracanie wartości mówiącej „sukces albo porażka” wprowadza błąd do systemu typów:

Odczytanie r.value przed sprawdzeniem r.ok to błąd kompilacji i właśnie o to chodzi. Wyjątków używaj dla rzeczy nieoczekiwanych (bugi, zerwane połączenie), a wartości Result dla wyników, o których musi zdecydować wywołujący.

Błędy w kodzie asynchronicznym

Odrzucona obietnica staje się rzuconym błędem w miejscu await, więc try/catch wokół await działa tak samo, a zmienna znowu ma typ unknown. Jedyna różnica to .catch() na obietnicy: parametr jego callbacku ma typ any, a nie unknown, więc dodaj adnotację samodzielnie (.catch((e: unknown) => ...)). Przykłady znajdziesz na stronie o async/await.

Częste błędy

  • Odczytywanie e.message bez zawężenia. Przy strict to się nie kompiluje; bez strict psuje się, gdy rzucone zostanie coś innego niż Error.
  • Łapanie wszystkiego i kontynuowanie. Obsługuj błędy, których się spodziewasz, a resztę rzucaj dalej.
  • Brak name we własnych błędach. Wtedy logi pokazują Error dla każdego z nich.
  • Rzucanie stringów. throw "failed" nie ma śladu stosu i nie przechodzi sprawdzeń instanceof Error.

Najczęściej zadawane pytania

Jaki typ ma błąd w bloku catch w TypeScript?

unknown, gdy włączony jest strict (opcja useUnknownInCatchVariables), a w przeciwnym razie any. JavaScript może rzucić dowolną wartość, nie tylko obiekty Error, więc TypeScript wymaga sprawdzenia. Zawęź ją przez if (e instanceof Error), zanim odczytasz e.message.

Czy mogę nadać zmiennej w catch typ Error w TypeScript?

Nie. catch (e: Error) daje błąd TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified. Dozwolone są tylko unknown i any, bo nic nie gwarantuje, co zostało rzucone. Zamiast tego zawężaj typ wewnątrz bloku.

Jak rzucić błąd w TypeScript?

Tak samo jak w JavaScripcie: throw new Error("message") albo instancją wbudowanej lub własnej podklasy, na przykład new RangeError(...). TypeScript pozwala rzucić dowolną wartość, ale rzucanie obiektów Error zachowuje ślad stosu i sprawia, że działają sprawdzenia instanceof Error.

Jak utworzyć własną klasę błędu w TypeScript?

Rozszerz Error, wywołaj super(message, options) i ustaw name: class NotFoundError extends Error { name = "NotFoundError"; }. Dodatkowe pola dodajesz w konstruktorze. Przy każdym obsługiwanym targecie (ES2015 i nowsze) instanceof NotFoundError działa bez poprawiania prototypu.

Czy TypeScript ma checked exceptions albo klauzulę throws?

Nie. Typ funkcji nic nie mówi o tym, co może ona rzucić, a kompilator nigdy nie sprawdza, czy błędy są obsłużone. Błędy, które wywołujący powinien obsłużyć, zwracaj jako wartości z typem Result (unią dyskryminowaną sukcesu i porażki), żeby system typów je sprawdzał.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ