Historia pytania: W JavaScript standardowe błędy są opisane za pomocą klasy Error. W dużych projektach TypeScript ważne jest, aby używać swojej hierarchii błędów do dokładnego przetwarzania sytuacji, ale są pewne niuanse dotyczące typów, dziedziczenia i zachowania 'instanceof'.
Problem: Przy dziedziczeniu po Error mogą wystąpić trudności: prototyp zostaje utracony, 'instanceof' działa niepoprawnie, typy mogą być opisane niepoprawnie, a serializacja często prowadzi do utraty stack trace. Bez jawnego określenia właściwości można napotkać błędy podczas przetwarzania błędów.
Rozwiązanie: Poprawnie definiować błędy użytkownika poprzez rozszerzenie klasy Error. Należy jawnie określić nazwę, ręcznie przywrócić prototyp (ważne dla ES5 i przy kompilacji do CommonJS), oraz typizować pola błędów.
Przykład kodu:
class ValidationError extends Error { code: number; constructor(message: string, code: number = 400) { super(message); Object.setPrototypeOf(this, ValidationError.prototype); this.name = 'ValidationError'; this.code = code; } } function process(user: string) { if (!user) throw new ValidationError('User required', 401); }
Kluczowe cechy:
Dlaczego nie można pominąć wywołania Object.setPrototypeOf(this, ...)?
Jeśli nie wywołasz Object.setPrototypeOf(this, Class.prototype), 'instanceof' ValidationError nie zadziała przy kompilacji do ES5/CommonJS lub Babel. Doprowadzi to do sytuacji, w której bloki catch ValidationError nie przechwycą błędu.
class CustomErr extends Error {} const err = new CustomErr('msg'); console.log(err instanceof CustomErr); // false bez setPrototypeOf
Czy można pomijać pole name w błędzie użytkownika?
Jeśli nie ustawisz właściwości this.name, stos błędów i wyświetlanie w logach będą niepoprawne, co utrudni znalezienie przyczyny i klasyfikację błędów.
Czy błędy powinny być serializowalne, a jeśli tak — jak?
Błędy powinny być poprawnie serializowane (na przykład do logowania lub przesyłania przez sieć), inaczej JSON.stringify(new Error()) nie zwróci message i stack. Należy nadpisać metodę toJSON.
class SerializableError extends Error { toJSON() { return { name: this.name, message: this.message, stack: this.stack }; } }
W projekcie wykonano po prostu class MyError extends Error, nie przywracając prototypu. Błędy były łapane przez if (err instanceof MyError), ale to nie działało, a kod cicho pomijał obsługę krytycznych sytuacji.
Plusy:
Minusy:
Zrealizowano poprawny CustomError, jawnie określono name, code i toJSON, testy pokryły przetwarzanie różnych typów błędów. W logach i obsługach catch struktura błędów stała się zrozumiała, dzięki czemu skrócił się czas poszukiwania błędów.
Plusy:
Minusy: