Chcę wrzucić pewne rzeczy do mojego kodu JS i chcę, aby były one wystąpieniem Error, ale chcę też, aby były czymś innym.
W Pythonie zwykle należałoby podklasować wyjątek.
Co należy zrobić w JS?
źródło
Chcę wrzucić pewne rzeczy do mojego kodu JS i chcę, aby były one wystąpieniem Error, ale chcę też, aby były czymś innym.
W Pythonie zwykle należałoby podklasować wyjątek.
Co należy zrobić w JS?
Jedynym standardowym polem Obiekt błędu jest message
właściwość. (Zobacz MDN lub Specyfikacja języka EcmaScript, rozdział 15.11) Wszystko inne zależy od platformy.
Środowisk Mosts ustawić stack
właściwość, ale fileName
i lineNumber
są praktycznie bezużyteczne być stosowane w spadku.
Zatem minimalistyczne podejście to:
function MyError(message) {
this.name = 'MyError';
this.message = message;
this.stack = (new Error()).stack;
}
MyError.prototype = new Error; // <-- remove this if you do not
// want MyError to be instanceof Error
Możesz powąchać stos, usunąć z niego niepożądane elementy i wyodrębnić informacje, takie jak fileName i lineNumber, ale zrobienie tego wymaga informacji o platformie, na której aktualnie działa JavaScript. W większości przypadków jest to niepotrzebne - i jeśli chcesz, możesz to zrobić pośmiertnie.
Safari jest godnym uwagi wyjątkiem. Nie ma żadnej stack
właściwości, ale throw
zestawy słów kluczowych sourceURL
i line
właściwości rzucanego obiektu. Te rzeczy są z pewnością poprawne.
Przypadki testowe, których użyłem, można znaleźć tutaj: własnoręcznie wykonane JavaScript Błąd porównywania obiektów .
this.name = 'MyError'
zewnętrzną funkcję i zmienić ją naMyError.prototype.name = 'MyError'
.function MyError(message) { this.message = message; this.stack = Error().stack; } MyError.prototype = Object.create(Error.prototype); MyError.prototype.name = "MyError";
MyError.prototype.constructor = MyError
.this
, prawda?W ES6:
źródło
źródło
var supportsClasses = false; try {eval('class X{}'); supportsClasses = true;} catch (e) {}
this.name = this.constructor.name;
zamiast tego.W skrócie:
Jeśli używasz ES6 bez transpilatorów :
Jeśli używasz transpilatora Babel :
Opcja 1: użyj rozszerzenia Babel-plugin-transform-builtin-extension
Opcja 2: zrób to sam (zainspirowany tą samą biblioteką)
Jeśli używasz czystego ES5 :
Alternatywnie: użyj szkieletu Classtrophobic
Wyjaśnienie:
Dlaczego rozszerzenie klasy Error za pomocą ES6 i Babel jest problemem?
Ponieważ wystąpienie CustomError nie jest już rozpoznawane jako takie.
W rzeczywistości, z oficjalnej dokumentacji Babel, to nie może przedłużyć jakikolwiek wbudowanych klas JavaScript , takich jak
Date
,Array
,DOM
lubError
.Problem opisano tutaj:
Co z innymi odpowiedziami SO?
Wszystkie podane odpowiedzi rozwiązują
instanceof
problem, ale tracisz regularny błądconsole.log
:Podczas korzystania z metody wspomnianej powyżej nie tylko naprawiasz
instanceof
problem, ale także utrzymujesz regularny błądconsole.log
:źródło
class CustomError extends Error { /* ... */}
nie obsługuje poprawnie argumentów specyficznych dla dostawcy (lineNumber
itp.), „Błąd rozszerzenia w JavaScript ze składnią ES6” jest specyficzny dla Babel, twoje rozwiązanie ES5 używaconst
i nie obsługuje niestandardowych argumentów.console.log(new CustomError('test') instanceof CustomError);// false
prawdą był w momencie pisania, ale został już rozwiązany. W rzeczywistości problem powiązany z odpowiedzią został rozwiązany i możemy przetestować poprawne zachowanie tutaj i wklejając kod w REPL i sprawdzając, jak jest poprawnie transponowany, aby utworzyć instancję z poprawnym łańcuchem prototypów.Edytować: Proszę przeczytać komentarze. Okazuje się, że działa to dobrze tylko w wersji V8 (Chrome / Node.JS). Moim zamiarem było udostępnienie rozwiązania dla różnych przeglądarek, które działałoby we wszystkich przeglądarkach, oraz zapewnienie śledzenia stosu tam, gdzie jest dostępna obsługa.
Edytować: Stworzyłem Wiki Wiki, aby umożliwić dalszą edycję.
Rozwiązanie dla V8 (Chrome / Node.JS), działa w Firefoksie i może być modyfikowane, aby działało głównie poprawnie w IE. (patrz koniec postu)
Oryginalny post na temat „Pokaż mi kod!”
Krótka wersja:
Trzymam się
this.constructor.prototype.__proto__ = Error.prototype
wewnątrz funkcji, aby zachować cały kod razem. Ale możesz też wymienićthis.constructor
zUserError
i że pozwala na przenoszenie kodu na zewnątrz funkcji, więc jest wywoływana tylko raz.Jeśli pójdziesz tą drogą, upewnij się, że nazywają tę linię przed pierwszym rzutem
UserError
.To zastrzeżenie nie stosuje funkcji, ponieważ funkcje są tworzone najpierw, bez względu na kolejność. W ten sposób możesz bez problemu przenieść funkcję na koniec pliku.
Kompatybilność z przeglądarkami
Działa w Firefox i Chrome (i Node.JS) i spełnia wszystkie obietnice.
Internet Explorer nie działa w następujący sposób
Błędy nie muszą
err.stack
zaczynać się, więc „to nie moja wina”.Error.captureStackTrace(this, this.constructor)
nie istnieje, więc musisz zrobić coś innegotoString
przestaje istnieć, gdy podklasujeszError
. Musisz także dodać.IE nie będzie uważał
UserError
za,instanceof Error
chyba że uruchomisz następujące przed tobąthrow UserError
źródło
Error.call(this)
w rzeczywistości nic nie robi, ponieważ zwraca błąd zamiast modyfikowaćthis
.UserError.prototype = Error.prototype
wprowadza w błąd. To nie robi dziedziczenia, czyni je tą samą klasą .Object.setPrototypeOf(this.constructor.prototype, Error.prototype)
jest to preferowanethis.constructor.prototype.__proto__ = Error.prototype
, przynajmniej dla obecnych przeglądarek.Aby uniknąć blaszki dla każdego rodzaju błędu, połączyłem mądrość niektórych rozwiązań w
createErrorType
funkcję:Następnie możesz łatwo zdefiniować nowe typy błędów w następujący sposób:
źródło
this.name = name;
?name
jest już ustawiony na prototypie, nie jest już potrzebny. Usunąłem to. Dzięki!W 2018 r roku uważam, że to najlepszy sposób; który obsługuje IE9 + i nowoczesne przeglądarki.
AKTUALIZACJA : Zobacz ten test i repozytorium, aby porównać różne implementacje.
Uważaj również na to, że
__proto__
własność jest przestarzała, co jest szeroko stosowane w innych odpowiedziach.źródło
setPrototypeOf()
? Przynajmniej według MDN ogólnie odradza się go używać, jeśli możesz osiągnąć to samo, po prostu ustawiając.prototype
właściwość w konstruktorze (tak jak robisz welse
bloku dla przeglądarek, które go nie mająsetPrototypeOf
).setPrototypeOf
. Ale jeśli nadal go potrzebujesz (jak prosi OP), powinieneś użyć wbudowanej metodologii. Jak wskazuje MDN, jest to uważane za właściwy sposób ustawienia prototypu obiektu. Innymi słowy, MDN mówi, że nie zmieniaj prototypu (ponieważ wpływa to na wydajność i optymalizację), ale jeśli musisz, użyjsetPrototypeOf
.CustomError.prototype = Object.create(Error.prototype)
). PonadtoObject.setPrototypeOf(CustomError, Error.prototype)
ustawia prototyp samego konstruktora, a nie określa prototyp dla nowych instancjiCustomError
. W każdym razie w 2016 roku uważam, że istnieje lepszy sposób na rozszerzenie błędów, chociaż wciąż zastanawiam się, jak używać tego razem z BabelemCustomError.prototype = Object.create(Error.prototype)
zmienia również prototyp. Musisz to zmienić, ponieważ w ES5 nie ma wbudowanej logiki rozszerzania / dziedziczenia. Jestem pewien, że wspomniana wtyczka babel robi podobne rzeczy.Object.setPrototypeOf
nie ma tutaj sensu, a przynajmniej nie w sposób, w jaki go używasz: gist.github.com/mbrowne/4af54767dcb3d529648f5a8aa11d6348 . Być może chciałeś napisaćObject.setPrototypeOf(CustomError.prototype, Error.prototype)
- to miałoby nieco więcej sensu (choć nadal nie przynosi żadnych korzyści w porównaniu do zwykłego ustawieniaCustomError.prototype
).Dla kompletności - tylko dlatego, że żadna z poprzednich odpowiedzi nie wspominała o tej metodzie - jeśli pracujesz z Node.js i nie musisz się martwić kompatybilnością przeglądarki, pożądany efekt jest dość łatwy do osiągnięcia dzięki wbudowanemu
inherits
zutil
modułu ( oficjalnych docs tutaj ).Załóżmy na przykład, że chcesz utworzyć niestandardową klasę błędów, która przyjmuje kod błędu jako pierwszy argument, a komunikat o błędzie jako drugi argument:
plik custom-error.js :
Teraz możesz utworzyć instancję i przekazać / rzucić
CustomError
:Zauważ, że dzięki temu fragmentowi ślad stosu będzie miał poprawną nazwę pliku i linię, a wystąpienie błędu będzie miało poprawną nazwę!
Dzieje się tak ze względu na użycie
captureStackTrace
metody, która tworzystack
właściwość na obiekcie docelowym (w tym przypadku jestCustomError
tworzona). Aby uzyskać więcej informacji o tym, jak to działa, sprawdź dokumentację tutaj .źródło
this.message = this.message;
czy to źle, czy są jeszcze szalone rzeczy, których nie wiem o JS?Wysoko głosowana odpowiedź Crescent Fresh jest myląca. Chociaż jego ostrzeżenia są nieważne, istnieją inne ograniczenia, których nie rozwiązuje.
Po pierwsze, rozumowanie w akapicie „Ostrzeżeń:” Crescenta nie ma sensu. Wyjaśnienie to sugeruje, że kodowanie „paczki if (błąd MyError) else ...”) jest w pewien sposób uciążliwe lub pełne w porównaniu do wielu instrukcji catch. Wiele instancji instrukcji w jednym bloku catch jest tak samo zwięzłych jak wiele instrukcji catch - czysty i zwięzły kod bez żadnych sztuczek. Jest to świetny sposób na emulację doskonałej obsługi błędów specyficznych dla podtypów Java.
WRT „pojawia się właściwość komunikatu podklasy nie została ustawiona”, nie jest tak w przypadku użycia poprawnie skonstruowanej podklasy Error. Aby utworzyć własną podklasę ErrorX Error, po prostu skopiuj blok kodu zaczynający się od „var MyError =”, zmieniając jedno słowo „MyError” na „ErrorX”. (Jeśli chcesz dodać niestandardowe metody do swojej podklasy, postępuj zgodnie z przykładowym tekstem).
Rzeczywistym i znaczącym ograniczeniem podklasowania błędów JavaScript jest to, że w implementacjach JavaScript lub debuggerach, które śledzą i raportują śledzenie stosu i lokalizację wystąpienia, takie jak FireFox, lokalizacja we własnej implementacji podklasy Error zostanie zapisana jako punkt tworzenia wystąpienia klasy, natomiast jeśli użyjesz bezpośredniego błędu, będzie to miejsce, w którym uruchomiłeś „nowy błąd (...)”). Użytkownicy IE prawdopodobnie nigdy tego nie zauważą, ale użytkownicy Fire Bug na FF zobaczą bezużyteczne wartości nazw plików i numerów linii zgłaszane obok tych błędów i będą musieli przejść do szczegółowego śledzenia stosu do elementu nr 1, aby znaleźć rzeczywistą lokalizację wystąpienia.
źródło
Crescent Fresh's
została usunięta!Co powiesz na to rozwiązanie?
Zamiast zgłaszać niestandardowy błąd za pomocą:
Owinąłbyś obiekt Error (trochę jak dekorator):
Dzięki temu wszystkie atrybuty są poprawne, takie jak stos, nazwa_pliku nazwa_wiersza, itd.
Wystarczy, że skopiujesz atrybuty lub zdefiniujesz dla nich metody pobierające. Oto przykład użycia getters (IE9):
źródło
new MyErr (arg1, arg2, new Error())
w konstruktorze MyErr używamyObject.assign
do przypisania właściwości ostatniego argumentu dothis
Jak powiedzieli niektórzy, z ES6 jest dość łatwo:
Wypróbowałem to w mojej aplikacji (Angular, maszynopis) i po prostu nie działało. Po pewnym czasie odkryłem, że problem pochodzi z Typescript: O
Zobacz https://github.com/Microsoft/TypeScript/issues/13965
To bardzo niepokojące, ponieważ jeśli:
W węźle lub bezpośrednio w przeglądarce wyświetli:
Custom error
Spróbuj uruchomić to z Typescript w swoim projekcie na placu zabaw Typescript, wyświetli się
Basic error
...Rozwiązaniem jest wykonanie następujących czynności:
źródło
Moje rozwiązanie jest prostsze niż inne udzielone odpowiedzi i nie ma wad.
Zachowuje łańcuch prototypów Error i wszystkie właściwości w Error, bez potrzeby ich szczegółowej znajomości. Został przetestowany w Chrome, Firefox, Node i IE11.
Jedynym ograniczeniem jest dodatkowy wpis na górze stosu wywołań. Ale łatwo to zignorować.
Oto przykład z dwoma niestandardowymi parametrami:
Przykładowe użycie:
W środowiskach wymagających polyfil z setPrototypeOf:
źródło
throw CustomError('err')
zamiastthrow new CustomError('err')
W powyższym przykładzie
Error.apply
(teżError.call
) nic mi nie robi (Firefox 3.6 / Chrome 5). Obejście, którego używam to:źródło
W Node, jak powiedzieli inni, jest to proste:
źródło
Chcę tylko dodać do tego, co inni już stwierdzili:
Aby upewnić się, że niestandardowa klasa błędów wyświetla się poprawnie w śladzie stosu, należy ustawić właściwość name prototypu niestandardowej klasy błędów na właściwość name niestandardowej klasy błędów. To jest to co mam na mysli:
Pełny przykład to:
Kiedy wszystko jest już powiedziane i zrobione, rzucasz swój nowy wyjątek i wygląda to tak (leniwie wypróbowałem to w narzędziach programistycznych chrome):
źródło
Moje 2 centy:
Dlaczego kolejna odpowiedź?
a) Ponieważ dostęp do
Error.stack
nieruchomości (jak w niektórych odpowiedziach) wiąże się z dużą utratą wydajności.b) Ponieważ jest to tylko jedna linia.
c) Ponieważ rozwiązanie na https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error nie wydaje się zachowywać informacji o stosie.
przykład użycia
http://jsfiddle.net/luciotato/xXyeB/
Co to robi?
this.__proto__.__proto__
jestMyError.prototype.__proto__
, więc ustawia__proto__
WSZYSTKIE INSTANCJE MyError na określony nowo utworzony Błąd. Zachowuje właściwości i metody klasy MyError, a także umieszcza nowe właściwości Error (w tym .stack) w__proto__
łańcuchu.Oczywisty problem:
Nie można mieć więcej niż jednego wystąpienia MyError z przydatnymi informacjami o stosie.
Nie używaj tego rozwiązania, jeśli nie w pełni rozumiesz, co to
this.__proto__.__proto__=
robi.źródło
Ponieważ wyjątki JavaScript są trudne do podklasowania, nie podklasuję. Właśnie tworzę nową klasę wyjątków i używam w niej błędu. Zmieniam właściwość Error.name, aby wyglądała jak mój niestandardowy wyjątek na konsoli:
Powyższy nowy wyjątek może zostać zgłoszony jak zwykły błąd i będzie działał zgodnie z oczekiwaniami, na przykład:
Uwaga: ślad stosu nie jest idealny, ponieważ doprowadzi cię do miejsca, w którym powstaje nowy błąd, a nie do miejsca, w którym rzucasz. Nie jest to wielka sprawa w Chrome, ponieważ zapewnia pełny ślad stosu bezpośrednio w konsoli. Ale na przykład jest to bardziej problematyczne w Firefoksie.
źródło
m = new InvalidInputError(); dontThrowMeYet(m);
m = new ...
wtedyPromise.reject(m)
. Nie jest to konieczne, ale kod jest łatwiejszy do odczytania.Jak wskazano w odpowiedzi Mohsena, w ES6 możliwe jest rozszerzanie błędów za pomocą klas. Jest to o wiele łatwiejsze, a ich zachowanie jest bardziej spójne z błędami natywnymi ... ale niestety nie jest to łatwe w użyciu w przeglądarce, jeśli potrzebujesz obsługi przeglądarek starszych niż ES6. Zobacz poniżej kilka uwag na temat tego, jak można to zaimplementować, ale tymczasem sugeruję stosunkowo proste podejście, które zawiera jedne z najlepszych sugestii z innych odpowiedzi:
W ES6 jest to tak proste, jak:
... i możesz wykryć obsługę klas ES6 za pomocą
try {eval('class X{}')
, ale dostaniesz błąd składniowy, jeśli spróbujesz dołączyć wersję ES6 do skryptu ładowanego przez starsze przeglądarki. Zatem jedynym sposobem obsługi wszystkich przeglądarek byłoby dynamiczne ładowanie osobnego skryptu (np. Przez AJAX lubeval()
) dla przeglądarek obsługujących ES6. Kolejną komplikacją jest to, żeeval()
nie jest obsługiwana we wszystkich środowiskach (ze względu na zasady bezpieczeństwa treści), co może, ale nie musi, być rozważane dla twojego projektu.Na razie więc pierwsze powyższe podejście lub po prostu
Error
bezpośrednie użycie bez próby rozszerzenia wydaje się najlepsze, co można praktycznie zrobić dla kodu, który musi obsługiwać przeglądarki inne niż ES6.Jest jeszcze jedno podejście, które niektórzy mogą rozważyć, polegające na użyciu,
Object.setPrototypeOf()
tam gdzie jest to możliwe, do utworzenia obiektu błędu, który jest instancją niestandardowego typu błędu, ale który wygląda i zachowuje się bardziej jak natywny błąd w konsoli (dzięki odpowiedzi Bena dla zalecenia). Oto moje podejście do tego podejścia: https://gist.github.com/mbrowne/fe45db61cea7858d11be933a998926a8 . Ale biorąc pod uwagę, że pewnego dnia będziemy mogli korzystać z ES6, osobiście nie jestem pewien, czy złożoność tego podejścia jest tego warta.źródło
Sposobem na to, aby to zrobić, jest zwrócenie wyniku zastosowania z konstruktora, a także ustawienie prototypu w zwykły skomplikowany sposób javascripty:
Jedyne problemy z tym sposobem zrobienia tego w tym momencie (trochę to powtórzyłem) to:
stack
imessage
nie są uwzględnione wMyError
iPierwszy problem można rozwiązać, wykonując iterację wszystkich niepoliczalnych właściwości błędu za pomocą sztuczki zawartej w tej odpowiedzi: Czy możliwe jest uzyskanie niepoliczalnej liczby dziedziczonych nazw właściwości obiektu? , ale nie jest to obsługiwane przez ie <9. Drugi problem można rozwiązać, odrywając tę linię ze śladu stosu, ale nie jestem pewien, jak bezpiecznie to zrobić (może po prostu usuwając drugą linię e.stack.toString () ??).
źródło
Fragment pokazuje to wszystko.
źródło
Zrobiłbym krok do tyłu i zastanowiłbym się, dlaczego chcesz to zrobić? Myślę, że chodzi o to, aby inaczej radzić sobie z różnymi błędami.
Na przykład w Pythonie możesz ograniczyć instrukcję catch tylko do catch
MyValidationError
, a być może chcesz zrobić coś podobnego w javascript.Nie możesz tego zrobić w javascript. Będzie tylko jeden blok catch. Powinieneś użyć instrukcji if w błędzie, aby określić jego typ.
catch(e) { if(isMyValidationError(e)) { ... } else { // maybe rethrow? throw e; } }
Myślę, że zamiast tego rzuciłbym surowy obiekt o typie, komunikacie i dowolnych innych właściwościach, które uznasz za stosowne.
A kiedy złapiesz błąd:
źródło
error.stack
, standardowe narzędzia nie będą z nim współpracować itp. Lepszym sposobem byłoby dodanie właściwości do wystąpienia błędu, np.var e = new Error(); e.type = "validation"; ...
Niestandardowy dekorator błędów
Jest to oparte na odpowiedzi George'a Baileya , ale rozszerza i upraszcza oryginalny pomysł. Jest napisany w CoffeeScript, ale łatwo go przekonwertować na JavaScript. Chodzi o to, aby rozszerzyć niestandardowy błąd Baileya za pomocą dekoratora, który go otacza, umożliwiając łatwe tworzenie nowych niestandardowych błędów.
Uwaga: Działa to tylko w wersji V8. W
Error.captureStackTrace
innych środowiskach nie ma wsparcia .Definiować
Dekorator przyjmuje nazwę typu błędu i zwraca funkcję, która pobiera komunikat o błędzie i dołącza nazwę błędu.
Posługiwać się
Teraz można łatwo tworzyć nowe typy błędów.
Dla zabawy możesz teraz zdefiniować funkcję, która wyrzuca a
SignatureError
jeśli zostanie wywołana ze zbyt dużą liczbą argumentów.Zostało to całkiem dobrze przetestowane i wydaje się działać idealnie na V8, utrzymując ślad, pozycję itp.
Uwaga: Używanie
new
jest opcjonalne podczas konstruowania niestandardowego błędu.źródło
jeśli nie zależy Ci na wynikach błędów, jest to najmniejsze co możesz zrobić
możesz go używać bez nowego tylko MyError (wiadomość)
Zmieniając prototyp po wywołaniu błędu konstruktora, nie musimy ustawiać stosu wywołań i komunikatu
źródło
Mohsen ma świetną odpowiedź powyżej w ES6, która określa nazwę, ale jeśli używasz TypeScript lub żyjesz w przyszłości, gdzie mam nadzieję, że ta propozycja dla pól klasy publicznej i prywatnej przeszła przez etap 3 jako propozycja i sprawiła, że do etapu 4 w ramach ECMAScript / JavaScript, możesz chcieć wiedzieć, że jest to tylko trochę krótsze. W etapie 3 przeglądarki zaczynają implementować funkcje, więc jeśli przeglądarka obsługuje tę funkcję, poniższy kod może po prostu działać. (Testowane w nowej przeglądarce Edge v81 wydaje się działać dobrze). Ostrzegamy jednak, że jest to obecnie funkcja niestabilna i należy z niej korzystać ostrożnie. Zawsze należy sprawdzać obsługę przeglądarki w przypadku funkcji niestabilnych. Ten post jest przeznaczony głównie dla przyszłych mieszkańców, gdy przeglądarki mogą to obsługiwać. Aby sprawdzić wsparcie, sprawdź MDNmogę użyć . Obecnie ma 66% wsparcia na rynku przeglądarek, co się tam pojawia, ale nie jest tak świetne, więc jeśli naprawdę chcesz go teraz użyć i nie chcesz czekać ani na transpilator, jak Babel, ani na coś takiego jak TypeScript .i
Porównaj to z bezimiennym błędem, który po rzuceniu nie zarejestruje swojej nazwy.
źródło