Kiedy coś rozwijamy, często potrzebujemy własnych klas błędów, aby odzwierciedlić konkretne rzeczy, które mogą pójść nie tak w naszych zadaniach. Dla błędów w operacjach sieciowych możemy potrzebować HttpError
, dla operacji bazodanowych DbError
, dla operacji wyszukiwania NotFoundError
I tak dalej.
nasze błędy powinny obsługiwać podstawowe właściwości błędów, takie jak message
, name
I, najlepiej, stack
. Ale mogą mieć również inne własne właściwości, np., HttpError
obiekty mogą miećstatusCode
właściwość o wartości takiej jak404
lub403
lub500
.
JavaScript pozwala używać throw
z dowolnym argumentem, więc technicznie nasze niestandardowe klasy błędów nie muszą dziedziczyć z Error
. Ale jeśli dziedziczymy, wtedy można użyć obj instanceof Error
do identyfikacji obiektów błędu. Więc lepiej dziedziczyć po nim.
wraz z rozwojem aplikacji, nasze własne błędy naturalnie tworzą hierarchię., Na przykład HttpTimeoutError
może dziedziczyć z HttpError
I tak dalej.
błąd rozszerzenia
jako przykład rozważmy funkcjęreadUser(json)
, która powinna odczytać JSON z danymi użytkownika.
oto przykład, jak poprawny json
może wyglądać:
let json = `{ "name": "John", "age": 30 }`;
wewnętrznie użyjemyJSON.parse
. Jeśli otrzyma zniekształcony json
, to rzuca SyntaxError
., Ale nawet jeśli json
jest poprawny składniowo, to nie znaczy, że jest prawidłowym użytkownikiem, prawda? Może zabraknąć niezbędnych danych. Na przykład, może nie mieć name
I age
właściwości, które są niezbędne dla naszych użytkowników.
Nasza funkcjareadUser(json)
nie tylko odczyta JSON, ale sprawdza („validate”) dane. Jeśli nie ma wymaganych pól lub format jest nieprawidłowy, oznacza to błąd. I to nie jest SyntaxError
, ponieważ dane są poprawne składniowo, ale inny rodzaj błędu., Nazwiemy go ValidationError
I utworzymy dla niego klasę. Błąd tego rodzaju powinien również zawierać informacje o polu obrażenia.
nasza ValidationError
klasa powinna dziedziczyć z wbudowanej klasy Error
.
Ta klasa jest wbudowana, ale oto jej przybliżony kod, więc możemy zrozumieć, co rozszerzamy:
teraz dziedziczmy z niej ValidationError
I wypróbujmy ją w akcji:
uwaga: w linii (1)
wywołujemy konstruktor nadrzędny., JavaScript wymaga od nas wywołania super
w konstruktorze potomnym, więc jest to obowiązkowe. Konstruktor nadrzędny ustawia właściwość message
.
konstruktor nadrzędny ustawia również właściwośćname
na"Error"
, więc w linii(2)
resetujemy ją do odpowiedniej wartości.,
spróbujmy użyć go w readUser(json)
:
try..catch
blok w powyższym kodzie obsługuje zarówno nasz ValidationError
, jak i wbudowany SyntaxError
z JSON.parse
.
proszę spojrzeć na to, jak używamyinstanceof
, aby sprawdzić konkretny typ błędu w linii(*)
.,
możemy również spojrzeć na err.name
, w ten sposób:
// ...// instead of (err instanceof SyntaxError)} else if (err.name == "SyntaxError") { // (*)// ...
wersjainstanceof
jest znacznie lepsza, ponieważ w przyszłości zamierzamy rozszerzyćValidationError
, tworzyć jego podtypy, takie jakPropertyRequiredError
. Iinstanceof
sprawdzenie będzie nadal działać dla nowych klas dziedziczących. Więc to jest przyszłościowe.
ważne jest również to, że jeśli catch
spotka nieznany błąd, to zostanie on umieszczony w linii (**)
., Blok catch
wie tylko, jak obsłużyć błędy walidacji i składni, inne rodzaje (z powodu literówki w kodzie lub innych nieznanych) powinny wpaść.
dalsze dziedziczenie
KlasaValidationError
jest bardzo ogólna. Wiele rzeczy może pójść nie tak. Właściwość może być nieobecna lub może być w złym formacie (np. wartość łańcuchowa dla age
). Zróbmy bardziej konkretną klasę PropertyRequiredError
, dokładnie dla nieobecnych właściwości. Będzie zawierać dodatkowe informacje o zaginionej nieruchomości.,
nowa klasa PropertyRequiredError
jest łatwa w użyciu: wystarczy podać nazwę właściwości: new PropertyRequiredError(property)
. message
jest generowany przez konstruktor.
należy pamiętać, żethis.name
wPropertyRequiredError
konstruktor jest ponownie przypisany ręcznie. Może to być nieco uciążliwe – przypisanie this.name = <class name>
w każdej niestandardowej klasie błędów. Możemy tego uniknąć, tworząc własną klasę „basic error”, która przypisuje this.name = this.constructor.name
. A potem odziedzicz z niego wszystkie nasze niestandardowe błędy.,
nazwijmy toMyError
.
oto kod zMyError
I inne niestandardowe klasy błędów, uproszczone:
teraz niestandardowe błędy są znacznie krótsze, zwłaszczaValidationError
, ponieważ pozbyliśmy się linii"this.name = ..."
w konstruktorze.
owijanie WYJĄTKÓW
celem funkcjireadUser
w powyższym kodzie jest „odczytanie danych użytkownika”. W procesie mogą wystąpić różnego rodzaju błędy., Obecnie mamySyntaxError
IValidationError
, ale w przyszłościreadUser
funkcja może rosnąć i prawdopodobnie generować inne rodzaje błędów.
kod wywołującyreadUser
powinien obsłużyć te błędy. Obecnie używa wieluif
s w blokucatch
, które sprawdzają klasę i obsługują znane błędy oraz sprawdzają nieznane.
schemat wygląda tak:
w powyższym kodzie widzimy dwa rodzaje błędów, ale może ich być więcej.,
Jeśli funkcjareadUser
generuje kilka rodzajów błędów, powinniśmy zadać sobie pytanie: czy naprawdę chcemy sprawdzać wszystkie typy błędów jeden po drugim za każdym razem?
często odpowiedź brzmi „nie”: chcielibyśmy być „o jeden poziom ponad wszystko”. Chcemy tylko wiedzieć, czy wystąpił „błąd odczytu danych” – dlaczego dokładnie tak się stało, jest często nieistotne (opisywany jest komunikat o błędzie). Albo, jeszcze lepiej, chcielibyśmy mieć sposób, aby uzyskać szczegóły błędu, ale tylko wtedy, gdy musimy.
technika, którą tutaj opisujemy nazywa się „owijanie WYJĄTKÓW”.,
- Stworzymy nową klasę
ReadError
aby reprezentować ogólny błąd „odczytu danych”. - funkcja
readUser
wychwytuje błędy odczytu danych, które występują wewnątrz niej, takie jakValidationError
ISyntaxError
I generujeReadError
. - obiekt
ReadError
zachowa odniesienie do oryginalnego błędu w swojej właściwościcause
.,
wtedy kod wywołujący readUser
będzie musiał tylko sprawdzić ReadError
, a nie dla każdego rodzaju błędów odczytu danych. A jeśli potrzebuje więcej szczegółów błędu, może sprawdzić swoją właściwość cause
.,
oto kod, który definiuje ReadError
I demonstruje jego użycie w readUser
I try..catch
:
w powyższym kodzie readUser
działa dokładnie tak, jak opisano – wyłapuje błędy składni i walidacji i zamiast tego wyrzuca ReadError
(nieznane błędy są powtarzane jak zwykle).
więc Zewnętrzny kod sprawdza instanceof ReadError
I tyle. Nie ma potrzeby wyświetlania wszystkich możliwych typów błędów.,
podejście nazywa się „zawijanie wyjątków”, ponieważ bierzemy wyjątki” niskiego poziomu ” i „zawijamy” je do ReadError
, co jest bardziej abstrakcyjne. Jest szeroko stosowany w programowaniu obiektowym.
podsumowanie
- możemy dziedziczyć z
Error
I innych wbudowanych klas błędów normalnie. Musimy tylko zadbać o Właściwośćname
I nie zapomnij zadzwonićsuper
. - możemy użyć
instanceof
aby sprawdzić konkretne błędy. Działa również z dziedziczeniem., Ale czasami mamy obiekt błędu pochodzący z biblioteki innej firmy i nie ma łatwego sposobu, aby uzyskać jego klasę. Następnie można zastosować właściwośćname
do takich sprawdzeń. - owijanie wyjątków jest powszechną techniką: funkcja obsługuje wyjątki niskiego poziomu i tworzy błędy wyższego poziomu zamiast różnych błędów niskiego poziomu. Wyjątki niskiego poziomu czasami stają się właściwościami tego obiektu, takimi jak
err.cause
w powyższych przykładach, ale nie jest to ściśle wymagane.