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?
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?
Odpowiedzi:
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 .
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:
class MyError extends Error {
constructor(message) {
super(message);
this.name = 'MyError';
}
}
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 :
class CustomError extends Error { /* ... */}
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ą)
function CustomError(...args) {
const instance = Reflect.construct(Error, args);
Reflect.setPrototypeOf(instance, Reflect.getPrototypeOf(this));
return instance;
}
CustomError.prototype = Object.create(Error.prototype, {
constructor: {
value: Error,
enumerable: false,
writable: true,
configurable: true
}
});
Reflect.setPrototypeOf(CustomError, Error);
Jeśli używasz czystego ES5 :
function CustomError(message, fileName, lineNumber) {
var instance = new Error(message, fileName, lineNumber);
Object.setPrototypeOf(instance, Object.getPrototypeOf(this));
return instance;
}
CustomError.prototype = Object.create(Error.prototype, {
constructor: {
value: Error,
enumerable: false,
writable: true,
configurable: true
}
});
if (Object.setPrototypeOf){
Object.setPrototypeOf(CustomError, Error);
} else {
CustomError.__proto__ = Error;
}
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.
class CustomError extends Error {}
console.log(new CustomError('test') instanceof Error);// true
console.log(new CustomError('test') instanceof CustomError);// false
W rzeczywistości, z oficjalnej dokumentacji Babel, to nie może przedłużyć jakikolwiek wbudowanych klas JavaScript , takich jak Date
, Array
, DOM
lub Error
.
Problem opisano tutaj:
Co z innymi odpowiedziami SO?
Wszystkie podane odpowiedzi rozwiązują instanceof
problem, ale tracisz regularny błąd console.log
:
console.log(new CustomError('test'));
// output:
// CustomError {name: "MyError", message: "test", stack: "Error↵ at CustomError (<anonymous>:4:19)↵ at <anonymous>:1:5"}
Podczas korzystania z metody wspomnianej powyżej nie tylko naprawiasz instanceof
problem, ale także utrzymujesz regularny błąd console.log
:
console.log(new CustomError('test'));
// output:
// Error: test
// at CustomError (<anonymous>:2:32)
// at <anonymous>:1:5
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żywa const
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)
function UserError(message) {
this.constructor.prototype.__proto__ = Error.prototype // Make this an instanceof Error.
Error.call(this) // Does not seem necessary. Perhaps remove this line?
Error.captureStackTrace(this, this.constructor) // Creates the this.stack getter
this.name = this.constructor.name; // Used to cause messages like "UserError: message" instead of the default "Error: message"
this.message = message; // Used to set the message
}
Oryginalny post na temat „Pokaż mi kod!”
Krótka wersja:
function UserError(message) {
this.constructor.prototype.__proto__ = Error.prototype
Error.captureStackTrace(this, this.constructor)
this.name = this.constructor.name
this.message = message
}
Trzymam się this.constructor.prototype.__proto__ = Error.prototype
wewnątrz funkcji, aby zachować cały kod razem. Ale możesz też wymienićthis.constructor
z UserError
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ś innego
if(Error.captureStackTrace) // AKA if not IE
Error.captureStackTrace(this, this.constructor)
toString
przestaje istnieć, gdy podklasujesz Error
. Musisz także dodać.
else
this.toString = function () { return this.name + ': ' + this.message }
IE nie będzie uważał UserError
za, instanceof Error
chyba że uruchomisz następujące przed tobąthrow UserError
UserError.prototype = Error.prototype
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 preferowane this.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ę:
function createErrorType(name, init) {
function E(message) {
if (!Error.captureStackTrace)
this.stack = (new Error()).stack;
else
Error.captureStackTrace(this, this.constructor);
this.message = message;
init && init.apply(this, arguments);
}
E.prototype = new Error();
E.prototype.name = name;
E.prototype.constructor = E;
return E;
}
Następnie możesz łatwo zdefiniować nowe typy błędów w następujący sposób:
var NameError = createErrorType('NameError', function (name, invalidChar) {
this.message = 'The name ' + name + ' may not contain ' + invalidChar;
});
var UnboundError = createErrorType('UnboundError', function (variableName) {
this.message = 'Variable ' + variableName + ' is not bound';
});
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.
function CustomError(message) {
Object.defineProperty(this, 'name', {
enumerable: false,
writable: false,
value: 'CustomError'
});
Object.defineProperty(this, 'message', {
enumerable: false,
writable: true,
value: message
});
if (Error.hasOwnProperty('captureStackTrace')) { // V8
Error.captureStackTrace(this, CustomError);
} else {
Object.defineProperty(this, 'stack', {
enumerable: false,
writable: false,
value: (new Error(message)).stack
});
}
}
if (typeof Object.setPrototypeOf === 'function') {
Object.setPrototypeOf(CustomError.prototype, Error.prototype);
} else {
CustomError.prototype = Object.create(Error.prototype, {
constructor: { value: CustomError }
});
}
Uważaj również na to, że __proto__
własność jest przestarzała, co jest szeroko stosowane w innych odpowiedziach.
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 w else
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żyj setPrototypeOf
.
CustomError.prototype = Object.create(Error.prototype)
). Ponadto Object.setPrototypeOf(CustomError, Error.prototype)
ustawia prototyp samego konstruktora, a nie określa prototyp dla nowych instancji CustomError
. 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 Babelem
CustomError.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 ustawienia CustomError.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
z util
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 :
'use strict';
var util = require('util');
function CustomError(code, message) {
Error.captureStackTrace(this, CustomError);
this.name = CustomError.name;
this.code = code;
this.message = message;
}
util.inherits(CustomError, Error);
module.exports = CustomError;
Teraz możesz utworzyć instancję i przekazać / rzucić CustomError
:
var CustomError = require('./path/to/custom-error');
// pass as the first argument to your callback
callback(new CustomError(404, 'Not found!'));
// or, if you are working with try/catch, throw it
throw new CustomError(500, 'Server Error!');
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 tworzy stack
właściwość na obiekcie docelowym (w tym przypadku jest CustomError
tworzona). Aby uzyskać więcej informacji o tym, jak to działa, sprawdź dokumentację tutaj .
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.
Crescent Fresh's
została usunięta!
Co powiesz na to rozwiązanie?
Zamiast zgłaszać niestandardowy błąd za pomocą:
throw new MyError("Oops!");
Owinąłbyś obiekt Error (trochę jak dekorator):
throw new MyError(Error("Oops!"));
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):
function MyError(wrapped)
{
this.wrapped = wrapped;
this.wrapped.name = 'MyError';
}
function wrap(attr)
{
Object.defineProperty(MyError.prototype, attr, {
get: function()
{
return this.wrapped[attr];
}
});
}
MyError.prototype = Object.create(Error.prototype);
MyError.prototype.constructor = MyError;
wrap('name');
wrap('message');
wrap('stack');
wrap('fileName');
wrap('lineNumber');
wrap('columnNumber');
MyError.prototype.toString = function()
{
return this.wrapped.toString();
};
new MyErr (arg1, arg2, new Error())
w konstruktorze MyErr używamy Object.assign
do przypisania właściwości ostatniego argumentu dothis
Jak powiedzieli niektórzy, z ES6 jest dość łatwo:
class CustomError extends Error { }
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:
class CustomError extends Error {}
try {
throw new CustomError()
} catch(e) {
if (e instanceof CustomError) {
console.log('Custom error');
} else {
console.log('Basic error');
}
}
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:
class CustomError extends Error {
// we have to do the following because of: https://github.com/Microsoft/TypeScript/issues/13965
// otherwise we cannot use instanceof later to catch a given type
public __proto__: Error;
constructor(message?: string) {
const trueProto = new.target.prototype;
super(message);
this.__proto__ = trueProto;
}
}
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:
function CustomError(message, param1, param2) {
var err = new Error(message);
Object.setPrototypeOf(err, CustomError.prototype);
err.param1 = param1;
err.param2 = param2;
return err;
}
CustomError.prototype = Object.create(
Error.prototype,
{name: {value: 'CustomError', enumerable: false}}
);
Przykładowe użycie:
try {
throw new CustomError('Something Unexpected Happened!', 1234, 'neat');
} catch (ex) {
console.log(ex.name); //CustomError
console.log(ex.message); //Something Unexpected Happened!
console.log(ex.param1); //1234
console.log(ex.param2); //neat
console.log(ex.stack); //stacktrace
console.log(ex instanceof Error); //true
console.log(ex instanceof CustomError); //true
}
W środowiskach wymagających polyfil z setPrototypeOf:
Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
obj.__proto__ = proto;
return obj;
};
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:
function MyError(message, fileName, lineNumber) {
var err = new Error();
if (err.stack) {
// remove one stack level:
if (typeof(Components) != 'undefined') {
// Mozilla:
this.stack = err.stack.substring(err.stack.indexOf('\n')+1);
}
else if (typeof(chrome) != 'undefined' || typeof(process) != 'undefined') {
// Google Chrome/Node.js:
this.stack = err.stack.replace(/\n[^\n]*/,'');
}
else {
this.stack = err.stack;
}
}
this.message = message === undefined ? err.message : message;
this.fileName = fileName === undefined ? err.fileName : fileName;
this.lineNumber = lineNumber === undefined ? err.lineNumber : lineNumber;
}
MyError.prototype = new Error();
MyError.prototype.constructor = MyError;
MyError.prototype.name = 'MyError';
W Node, jak powiedzieli inni, jest to proste:
class DumbError extends Error {
constructor(foo = 'bar', ...params) {
super(...params);
if (Error.captureStackTrace) {
Error.captureStackTrace(this, DumbError);
}
this.name = 'DumbError';
this.foo = foo;
this.date = new Date();
}
}
try {
let x = 3;
if (x < 10) {
throw new DumbError();
}
} catch (error) {
console.log(error);
}
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:
CustomError.prototype = Error.prototype;
CustomError.prototype.name = 'CustomError';
Pełny przykład to:
var CustomError = function(message) {
var err = new Error(message);
err.name = 'CustomError';
this.name = err.name;
this.message = err.message;
//check if there is a stack property supported in browser
if (err.stack) {
this.stack = err.stack;
}
//we should define how our toString function works as this will be used internally
//by the browser's stack trace generation function
this.toString = function() {
return this.name + ': ' + this.message;
};
};
CustomError.prototype = new Error();
CustomError.prototype.name = 'CustomError';
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):
CustomError: Stuff Happened. GASP!
at Error.CustomError (<anonymous>:3:19)
at <anonymous>:2:7
at Object.InjectedScript._evaluateOn (<anonymous>:603:39)
at Object.InjectedScript._evaluateAndWrap (<anonymous>:562:52)
at Object.InjectedScript.evaluate (<anonymous>:481:21)
Moje 2 centy:
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.
//MyError class constructor
function MyError(msg){
this.__proto__.__proto__ = Error.apply(null, arguments);
};
przykład użycia
http://jsfiddle.net/luciotato/xXyeB/
this.__proto__.__proto__
jest MyError.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.
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.
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:
var InvalidInputError = function(message) {
var error = new Error(message);
error.name = 'InvalidInputError';
return error;
};
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:
throw new InvalidInputError("Input must be a string");
// Output: Uncaught InvalidInputError: Input must be a string
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.
m = new InvalidInputError(); dontThrowMeYet(m);
m = new ...
wtedy Promise.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:
function CustomError(message) {
//This is for future compatibility with the ES6 version, which
//would display a similar message if invoked without the
//`new` operator.
if (!(this instanceof CustomError)) {
throw new TypeError("Constructor 'CustomError' cannot be invoked without 'new'");
}
this.message = message;
//Stack trace in V8
if (Error.captureStackTrace) {
Error.captureStackTrace(this, CustomError);
}
else this.stack = (new Error).stack;
}
CustomError.prototype = Object.create(Error.prototype);
CustomError.prototype.name = 'CustomError';
W ES6 jest to tak proste, jak:
class CustomError extends Error {}
... 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 lub eval()
) dla przeglądarek obsługujących ES6. Kolejną komplikacją jest to, że eval()
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.
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:
function MyError() {
var tmp = Error.apply(this, arguments);
tmp.name = this.name = 'MyError'
this.stack = tmp.stack
this.message = tmp.message
return this
}
var IntermediateInheritor = function() {}
IntermediateInheritor.prototype = Error.prototype;
MyError.prototype = new IntermediateInheritor()
var myError = new MyError("message");
console.log("The message is: '"+myError.message+"'") // The message is: 'message'
console.log(myError instanceof Error) // true
console.log(myError instanceof MyError) // true
console.log(myError.toString()) // MyError: message
console.log(myError.stack) // MyError: message \n
// <stack trace ...>
Jedyne problemy z tym sposobem zrobienia tego w tym momencie (trochę to powtórzyłem) to:
stack
i message
nie są uwzględnione w MyError
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 () ??).
Fragment pokazuje to wszystko.
function add(x, y) {
if (x && y) {
return x + y;
} else {
/**
*
* the error thrown will be instanceof Error class and InvalidArgsError also
*/
throw new InvalidArgsError();
// throw new Invalid_Args_Error();
}
}
// Declare custom error using using Class
class Invalid_Args_Error extends Error {
constructor() {
super("Invalid arguments");
Error.captureStackTrace(this);
}
}
// Declare custom error using Function
function InvalidArgsError(message) {
this.message = `Invalid arguments`;
Error.captureStackTrace(this);
}
// does the same magic as extends keyword
Object.setPrototypeOf(InvalidArgsError.prototype, Error.prototype);
try{
add(2)
}catch(e){
// true
if(e instanceof Error){
console.log(e)
}
// true
if(e instanceof InvalidArgsError){
console.log(e)
}
}
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.
catch (MyValidationError e) {
....
}
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.
throw { type: "validation", message: "Invalid timestamp" }
A kiedy złapiesz błąd:
catch(e) {
if(e.type === "validation") {
// handle error
}
// re-throw, or whatever else
}
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"; ...
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 .
Dekorator przyjmuje nazwę typu błędu i zwraca funkcję, która pobiera komunikat o błędzie i dołącza nazwę błędu.
CoreError = (@message) ->
@constructor.prototype.__proto__ = Error.prototype
Error.captureStackTrace @, @constructor
@name = @constructor.name
BaseError = (type) ->
(message) -> new CoreError "#{ type }Error: #{ message }"
Teraz można łatwo tworzyć nowe typy błędów.
StorageError = BaseError "Storage"
SignatureError = BaseError "Signature"
Dla zabawy możesz teraz zdefiniować funkcję, która wyrzuca a SignatureError
jeśli zostanie wywołana ze zbyt dużą liczbą argumentów.
f = -> throw SignatureError "too many args" if arguments.length
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.
jeśli nie zależy Ci na wynikach błędów, jest to najmniejsze co możesz zrobić
Object.setPrototypeOf(MyError.prototype, Error.prototype)
function MyError(message) {
const error = new Error(message)
Object.setPrototypeOf(error, MyError.prototype);
return error
}
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
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
class EOFError extends Error {
name="EOFError"
}
throw new EOFError("Oops errored");
Porównaj to z bezimiennym błędem, który po rzuceniu nie zarejestruje swojej nazwy.
class NamelessEOFError extends Error {}
throw new NamelessEOFError("Oops errored");
this.name = 'MyError'
zewnętrzną funkcję i zmienić ją naMyError.prototype.name = 'MyError'
.