Ich möchte einige Dinge in meinen JS-Code werfen und möchte, dass sie eine Fehlerinstanz sind, aber ich möchte auch, dass sie etwas anderes sind.
In Python würde man normalerweise Exception unterordnen.
Was ist in JS angemessen?
Ich möchte einige Dinge in meinen JS-Code werfen und möchte, dass sie eine Fehlerinstanz sind, aber ich möchte auch, dass sie etwas anderes sind.
In Python würde man normalerweise Exception unterordnen.
Was ist in JS angemessen?
Antworten:
Das einzige Standardfeld, über das das Fehlerobjekt verfügt, ist die message
Eigenschaft. (Siehe MDN oder EcmaScript-Sprachspezifikation, Abschnitt 15.11.) Alles andere ist plattformspezifisch.
Mosts Umgebungen setzen Sie die stack
Eigenschaft, aber fileName
und lineNumber
sind praktisch nutzlos in Vererbung verwendet werden.
Der minimalistische Ansatz lautet also:
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
Sie können den Stapel abhören, unerwünschte Elemente daraus entfernen und Informationen wie Dateiname und Zeilennummer extrahieren. Dazu sind jedoch Informationen über die Plattform erforderlich, auf der JavaScript derzeit ausgeführt wird. In den meisten Fällen ist das unnötig - und Sie können es post mortem tun, wenn Sie es wirklich wollen.
Safari ist eine bemerkenswerte Ausnahme. Es gibt keine stack
Eigenschaft, aber die throw
Schlüsselwortsätze sourceURL
und line
Eigenschaften des Objekts, das ausgelöst wird. Diese Dinge sind garantiert richtig.
Von mir verwendete Testfälle finden Sie hier: JavaScript-selbst erstellter Fehlerobjektvergleich .
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
, oder?
In 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;
stattdessen die Wartung , um die Wartung zu vereinfachen .
Zusamenfassend:
Wenn Sie ES6 ohne Transpiler verwenden :
class CustomError extends Error { /* ... */}
Wenn Sie den Babel-Transpiler verwenden :
Option 1: Verwenden Sie Babel-Plugin-Transform-Builtin-Extend
Option 2: Mach es selbst (inspiriert von derselben Bibliothek)
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);
Wenn Sie reines ES5 verwenden :
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;
}
Alternative: verwenden Classtrophobic- Framework
Erläuterung:
Warum ist die Erweiterung der Fehlerklasse mit ES6 und Babel ein Problem?
Weil eine Instanz von CustomError nicht mehr als solche erkannt wird.
class CustomError extends Error {}
console.log(new CustomError('test') instanceof Error);// true
console.log(new CustomError('test') instanceof CustomError);// false
In der Tat, von der offiziellen Dokumentation von Babel, Sie können nicht verlängern jeden integrierte JavaScript - Klassen wie Date
, Array
, DOM
oderError
.
Das Problem wird hier beschrieben:
Was ist mit den anderen SO-Antworten?
Alle gegebenen Antworten beheben das instanceof
Problem, aber Sie verlieren den regulären Fehler console.log
:
console.log(new CustomError('test'));
// output:
// CustomError {name: "MyError", message: "test", stack: "Error↵ at CustomError (<anonymous>:4:19)↵ at <anonymous>:1:5"}
Mit der oben genannten Methode beheben Sie nicht nur das instanceof
Problem, sondern behalten auch den regulären Fehler bei console.log
:
console.log(new CustomError('test'));
// output:
// Error: test
// at CustomError (<anonymous>:2:32)
// at <anonymous>:1:5
class CustomError extends Error { /* ... */}
behandelt herstellerspezifische Argumente ( lineNumber
usw.) nicht korrekt. 'Fehler in Javascript mit ES6-Syntax erweitern' ist Babel-spezifisch, wird von Ihrer ES5-Lösung verwendet const
und es werden keine benutzerdefinierten Argumente verarbeitet.
console.log(new CustomError('test') instanceof CustomError);// false
zum Zeitpunkt des Schreibens wahr war, aber jetzt behoben wurde. Tatsächlich wurde das in der Antwort verknüpfte Problem behoben, und wir können das korrekte Verhalten hier testen und den Code in die REPL einfügen und sehen, wie er korrekt transpiliert wird, um mit der richtigen Prototypkette zu instanziieren.
Bearbeiten: Bitte lesen Sie die Kommentare. Es stellt sich heraus, dass dies nur in V8 (Chrome / Node.JS) gut funktioniert. Meine Absicht war es, eine browserübergreifende Lösung bereitzustellen, die in allen Browsern funktioniert, und Stack-Trace bereitzustellen, wenn Unterstützung vorhanden ist.
Bearbeiten: Ich habe dieses Community-Wiki erstellt, um mehr Bearbeitung zu ermöglichen.
Die Lösung für V8 (Chrome / Node.JS) funktioniert in Firefox und kann so geändert werden, dass sie im IE größtenteils korrekt funktioniert. (siehe Ende des Beitrags)
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
}
Originaler Beitrag zu "Zeig mir den Code!"
Kurzfassung:
function UserError(message) {
this.constructor.prototype.__proto__ = Error.prototype
Error.captureStackTrace(this, this.constructor)
this.name = this.constructor.name
this.message = message
}
Ich bleibe this.constructor.prototype.__proto__ = Error.prototype
in der Funktion, um den gesamten Code zusammenzuhalten. Sie können aber auch ersetzen this.constructor
mit UserError
und ermöglicht es Ihnen , den Code außerhalb der Funktion zu bewegen, so dass es nur einmal aufgerufen wird.
Wenn Sie diesen Weg gehen, stellen Sie sicher, dass Sie diese Leitung vor dem ersten Werfen anrufen UserError
.
Diese Einschränkung wendet die Funktion nicht an, da Funktionen unabhängig von der Reihenfolge zuerst erstellt werden. So können Sie die Funktion problemlos an das Ende der Datei verschieben.
Browser-Kompatibilität
Funktioniert in Firefox und Chrome (und Node.JS) und erfüllt alle Versprechen.
Internet Explorer schlägt im Folgenden fehl
Fehler müssen nicht damit err.stack
beginnen, also "es ist nicht meine Schuld".
Error.captureStackTrace(this, this.constructor)
existiert nicht, also musst du etwas anderes tun wie
if(Error.captureStackTrace) // AKA if not IE
Error.captureStackTrace(this, this.constructor)
toString
hört auf zu existieren, wenn Sie Unterklasse Error
. Sie müssen also auch hinzufügen.
else
this.toString = function () { return this.name + ': ' + this.message }
IE wird nicht als UserError
ein betrachtet , es instanceof Error
sei denn, Sie führen die folgenden einige Zeit vor Ihnen austhrow UserError
UserError.prototype = Error.prototype
Error.call(this)
tut in der Tat nichts, da es einen Fehler zurückgibt , anstatt ihn zu ändern this
.
UserError.prototype = Error.prototype
ist irreführend. Dies führt keine Vererbung durch, dies macht sie zur gleichen Klasse .
Object.setPrototypeOf(this.constructor.prototype, Error.prototype)
wird this.constructor.prototype.__proto__ = Error.prototype
zumindest für aktuelle Browser bevorzugt .
Um das Boilerplate für jede Art von Fehler zu vermeiden , habe ich die Weisheit einiger Lösungen zu einer createErrorType
Funktion zusammengefasst:
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;
}
Dann können Sie neue Fehlertypen einfach wie folgt definieren:
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
der Prototyp bereits eingestellt ist, ist er nicht mehr erforderlich. Ich habe es entfernt. Vielen Dank!
Im Jahr 2018 , ich denke , das ist die beste Art und Weise; das unterstützt IE9 + und moderne Browser.
UPDATE : In diesem Test und Repo finden Sie Vergleiche zu verschiedenen Implementierungen.
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 }
});
}
Beachten Sie auch, dass das __proto__
Eigentum veraltet ist, was in anderen Antworten häufig verwendet wird.
setPrototypeOf()
? Zumindest laut MDN wird generell davon abgeraten, es zu verwenden, wenn Sie dasselbe erreichen können, indem Sie einfach die .prototype
Eigenschaft für den Konstruktor else
festlegen (wie Sie es im Block für Browser tun, die keine haben setPrototypeOf
).
setPrototypeOf
. Wenn Sie es jedoch weiterhin benötigen (wie von OP verlangt), sollten Sie die integrierte Methodik verwenden. Wie MDN angibt, wird dies als die richtige Methode zum Festlegen des Prototyps eines Objekts angesehen. Mit anderen Worten, MDN sagt, ändern Sie den Prototyp nicht (da dies die Leistung und Optimierung beeinflusst), sondern verwenden Sie ihn, wenn Sie müssen setPrototypeOf
.
CustomError.prototype = Object.create(Error.prototype)
). Außerdem Object.setPrototypeOf(CustomError, Error.prototype)
wird der Prototyp des Konstruktors selbst festgelegt, anstatt den Prototyp für neue Instanzen von anzugeben CustomError
. Wie auch immer, 2016 gibt es meiner Meinung nach einen besseren Weg, um Fehler zu erweitern, obwohl ich immer noch herausfinde, wie ich sie zusammen mit Babel verwenden kann: github.com/loganfsmyth/babel-plugin-transform-builtin-extend/…
CustomError.prototype = Object.create(Error.prototype)
ändert auch den Prototyp. Sie müssen es ändern, da in ES5 keine integrierte Erweiterungs- / Vererbungslogik vorhanden ist. Ich bin sicher, dass das von Ihnen erwähnte Babel-Plugin ähnliche Dinge tut.
Object.setPrototypeOf
hier keinen Sinn ergibt, zumindest nicht so, wie Sie sie verwenden: gist.github.com/mbrowne/4af54767dcb3d529648f5a8aa11d6348 . Vielleicht Object.setPrototypeOf(CustomError.prototype, Error.prototype)
wollten Sie schreiben - das wäre etwas sinnvoller (obwohl es immer noch keinen Vorteil gegenüber dem einfachen Einstellen bietet CustomError.prototype
).
Der Vollständigkeit halber - nur weil in keiner der vorherigen Antworten diese Methode erwähnt wurde - ist der gewünschte Effekt mit der integrierten Funktion ziemlich einfach zu erzielen, wenn Sie mit Node.js arbeiten und sich nicht um die Browserkompatibilität kümmern müssen inherits
des util
Moduls ( offizielle Dokumente hier ).
Angenommen, Sie möchten eine benutzerdefinierte Fehlerklasse erstellen, die einen Fehlercode als erstes Argument und die Fehlermeldung als zweites Argument verwendet:
Datei 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;
Jetzt können Sie instanziieren und übergeben / werfen 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!');
Beachten Sie, dass mit diesem Snippet der Stack-Trace den richtigen Dateinamen und die richtige Zeile hat und die Fehlerinstanz den richtigen Namen hat!
Dies geschieht aufgrund der Verwendung der captureStackTrace
Methode, die eine stack
Eigenschaft für das Zielobjekt erstellt (in diesem Fall die CustomError
Instanziierung). Weitere Informationen zur Funktionsweise finden Sie in der Dokumentation hier .
this.message = this.message;
Ist das falsch oder gibt es noch verrückte Dinge, die ich über JS nicht weiß?
Die Antwort von Crescent Fresh ist irreführend. Obwohl seine Warnungen ungültig sind, gibt es andere Einschränkungen, die er nicht anspricht.
Erstens macht die Argumentation in Crescent 'Absatz "Vorbehalte:" keinen Sinn. Die Erklärung impliziert, dass das Codieren von "ein paar von if (Fehlerinstanz von MyError) else ..." im Vergleich zu mehreren catch-Anweisungen irgendwie lästig oder ausführlich ist. Mehrere Instanzen von Anweisungen in einem einzelnen catch-Block sind genauso präzise wie mehrere catch-Anweisungen - sauberer und prägnanter Code ohne Tricks. Dies ist eine großartige Möglichkeit, um die großartige Fehlerbehandlung für Java-Subtypen zu emulieren.
WRT "Es wird angezeigt, dass die Nachrichteneigenschaft der Unterklasse nicht festgelegt wird". Dies ist nicht der Fall, wenn Sie eine ordnungsgemäß erstellte Fehlerunterklasse verwenden. Um Ihre eigene ErrorX Error-Unterklasse zu erstellen, kopieren Sie einfach den Codeblock, der mit "var MyError =" beginnt, und ändern Sie das eine Wort "MyError" in "ErrorX". (Wenn Sie Ihrer Unterklasse benutzerdefinierte Methoden hinzufügen möchten, folgen Sie dem Beispieltext.)
Die eigentliche und wesentliche Einschränkung der JavaScript-Fehlerunterklasse besteht darin, dass bei JavaScript-Implementierungen oder -Debuggern, die die Stapelverfolgung und den Ort der Instanziierung verfolgen und darüber berichten, wie FireFox, ein Ort in Ihrer eigenen Fehlerunterklassenimplementierung als Instanziierungspunkt der aufgezeichnet wird Wenn Sie einen direkten Fehler verwenden, ist dies der Ort, an dem Sie "new Error (...)" ausgeführt haben. IE-Benutzer würden es wahrscheinlich nie bemerken, aber Benutzer von Fire Bug auf FF werden nutzlose Dateinamen- und Zeilennummernwerte sehen, die neben diesen Fehlern gemeldet werden, und müssen einen Drilldown auf dem Stack-Trace zu Element 1 durchführen, um den tatsächlichen Instanziierungsspeicherort zu finden.
Crescent Fresh's
sie gelöscht wurde!
Wie wäre es mit dieser Lösung?
Anstatt Ihren benutzerdefinierten Fehler zu werfen, verwenden Sie:
throw new MyError("Oops!");
Sie würden das Fehlerobjekt einschließen (ähnlich wie ein Dekorateur):
throw new MyError(Error("Oops!"));
Dadurch wird sichergestellt, dass alle Attribute korrekt sind, z. B. der Stapel, der Dateiname, die Zeilennummer usw.
Alles, was Sie dann tun müssen, ist entweder über die Attribute zu kopieren oder Getter für sie zu definieren. Hier ist ein Beispiel mit Getter (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())
und im MyErr-Konstruktor verwenden wir Object.assign
, um die Eigenschaften des letzten Args zuzuweisenthis
Wie einige Leute gesagt haben, ist es mit ES6 ziemlich einfach:
class CustomError extends Error { }
Also habe ich das in meiner App (Angular, Typescript) versucht und es hat einfach nicht funktioniert. Nach einiger Zeit habe ich festgestellt, dass das Problem von Typescript stammt: O.
Siehe https://github.com/Microsoft/TypeScript/issues/13965
Es ist sehr beunruhigend, denn wenn Sie dies tun:
class CustomError extends Error {}
try {
throw new CustomError()
} catch(e) {
if (e instanceof CustomError) {
console.log('Custom error');
} else {
console.log('Basic error');
}
}
Im Knoten oder direkt in Ihrem Browser wird Folgendes angezeigt: Custom error
Versuchen Sie, das mit Typescript in Ihrem Projekt auf dem Typescript-Spielplatz auszuführen, es wird angezeigt Basic error
...
Die Lösung besteht darin, Folgendes zu tun:
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;
}
}
Meine Lösung ist einfacher als die anderen Antworten und hat keine Nachteile.
Die Fehlerprototypkette und alle Eigenschaften von Error bleiben erhalten, ohne dass spezielle Kenntnisse erforderlich sind. Es wurde in Chrome, Firefox, Node und IE11 getestet.
Die einzige Einschränkung ist ein zusätzlicher Eintrag oben im Aufrufstapel. Das wird aber leicht ignoriert.
Hier ist ein Beispiel mit zwei benutzerdefinierten Parametern:
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}}
);
Anwendungsbeispiel:
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
}
Für Umgebungen, die eine Polydatei von setPrototypeOf erfordern:
Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
obj.__proto__ = proto;
return obj;
};
throw CustomError('err')
anstelle vonthrow new CustomError('err')
Im obigen Beispiel tut Error.apply
(auch Error.call
) nichts für mich (Firefox 3.6 / Chrome 5). Eine Problemumgehung, die ich verwende, ist:
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';
In Node ist es, wie andere gesagt haben, einfach:
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);
}
Ich möchte nur hinzufügen, was andere bereits gesagt haben:
Um sicherzustellen, dass die benutzerdefinierte Fehlerklasse ordnungsgemäß im Stack-Trace angezeigt wird, müssen Sie die Namenseigenschaft des Prototyps der benutzerdefinierten Fehlerklasse auf die Namenseigenschaft der benutzerdefinierten Fehlerklasse festlegen. Das ist was ich meine:
CustomError.prototype = Error.prototype;
CustomError.prototype.name = 'CustomError';
Das vollständige Beispiel wäre also:
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';
Wenn alles gesagt und getan ist, werfen Sie Ihre neue Ausnahme und es sieht so aus (ich habe dies in den Chrome Dev Tools faul versucht):
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)
Meine 2 Cent:
a) Weil der Zugriff auf die Error.stack
Immobilie (wie in einigen Antworten) eine große Leistungsminderung mit sich bringt.
b) Weil es nur eine Zeile ist.
c) Da die Lösung unter https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error keine Stapelinformationen zu speichern scheint.
//MyError class constructor
function MyError(msg){
this.__proto__.__proto__ = Error.apply(null, arguments);
};
Anwendungsbeispiel
http://jsfiddle.net/luciotato/xXyeB/
this.__proto__.__proto__
ist MyError.prototype.__proto__
, also setzt es __proto__
FOR ALL INSTANCES von MyError auf einen bestimmten neu erstellten Fehler. Es behält die Eigenschaften und Methoden der MyError-Klasse bei und fügt auch die neuen Fehlereigenschaften (einschließlich .stack) in die __proto__
Kette ein.
Sie können nicht mehr als eine Instanz von MyError mit nützlichen Stapelinformationen haben.
Verwenden Sie diese Lösung nicht, wenn Sie nicht vollständig verstehen, was this.__proto__.__proto__=
funktioniert.
Da JavaScript-Ausnahmen schwer zu unterklassifizieren sind, unterteile ich sie nicht. Ich erstelle gerade eine neue Ausnahmeklasse und verwende einen Fehler darin. Ich ändere die Eigenschaft Error.name so, dass sie wie meine benutzerdefinierte Ausnahme auf der Konsole aussieht:
var InvalidInputError = function(message) {
var error = new Error(message);
error.name = 'InvalidInputError';
return error;
};
Die obige neue Ausnahme kann wie ein regulärer Fehler ausgelöst werden und funktioniert wie erwartet, zum Beispiel:
throw new InvalidInputError("Input must be a string");
// Output: Uncaught InvalidInputError: Input must be a string
Vorsichtsmaßnahme: Die Stapelverfolgung ist nicht perfekt, da sie Sie dorthin bringt, wo der neue Fehler erstellt wurde und nicht dorthin, wo Sie werfen. Dies ist in Chrome keine große Sache, da Sie einen vollständigen Stack-Trace direkt in der Konsole erhalten. Bei Firefox ist dies jedoch problematischer.
m = new InvalidInputError(); dontThrowMeYet(m);
m = new ...
dann Promise.reject(m)
. Es ist keine Notwendigkeit, aber der Code ist leichter zu lesen.
Wie in Mohsens Antwort ausgeführt, ist es in ES6 möglich, Fehler mithilfe von Klassen zu erweitern. Es ist viel einfacher und ihr Verhalten ist konsistenter mit nativen Fehlern ... aber leider ist es nicht einfach, dies im Browser zu verwenden, wenn Sie Browser vor ES6 unterstützen müssen. Im Folgenden finden Sie einige Hinweise, wie dies implementiert werden könnte. In der Zwischenzeit schlage ich jedoch einen relativ einfachen Ansatz vor, der einige der besten Vorschläge aus anderen Antworten enthält:
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';
In ES6 ist es so einfach wie:
class CustomError extends Error {}
... und Sie können Unterstützung für ES6-Klassen mit erkennen try {eval('class X{}')
, aber Sie erhalten einen Syntaxfehler, wenn Sie versuchen, die ES6-Version in ein Skript aufzunehmen, das von älteren Browsern geladen wird. Die einzige Möglichkeit, alle Browser zu unterstützen, besteht darin, ein separates Skript dynamisch (z. B. über AJAX oder eval()
) für Browser zu laden , die ES6 unterstützen. Eine weitere Komplikation besteht darin, dass eval()
nicht in allen Umgebungen (aufgrund von Inhaltssicherheitsrichtlinien) unterstützt wird, was für Ihr Projekt möglicherweise eine Überlegung darstellt oder nicht.
Im Error
Moment scheint entweder der erste Ansatz oben oder die direkte Verwendung ohne den Versuch, ihn zu erweitern, der beste zu sein, der praktisch für Code durchgeführt werden kann, der Nicht-ES6-Browser unterstützen muss.
Es gibt einen anderen Ansatz, den einige Leute in Betracht ziehen könnten, nämlich, Object.setPrototypeOf()
wo verfügbar, ein Fehlerobjekt zu erstellen, das eine Instanz Ihres benutzerdefinierten Fehlertyps ist, das jedoch eher wie ein nativer Fehler in der Konsole aussieht und sich verhält (dank Bens Antwort) für die Empfehlung). Hier ist meine Meinung zu diesem Ansatz: https://gist.github.com/mbrowne/fe45db61cea7858d11be933a998926a8 . Angesichts der Tatsache, dass wir eines Tages nur ES6 verwenden können, bin ich mir persönlich nicht sicher, ob sich die Komplexität dieses Ansatzes lohnt.
Der Weg, dies richtig zu machen, besteht darin, das Ergebnis der Anwendung vom Konstruktor zurückzugeben und den Prototyp auf die übliche komplizierte Javascripty-Weise zu setzen:
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 ...>
Die einzigen Probleme mit dieser Art, es an diesem Punkt zu tun (ich habe es ein bisschen wiederholt), sind das
stack
und message
sind nicht in MyError
und enthaltenDas erste Problem könnte behoben werden, indem alle nicht aufzählbaren Fehlereigenschaften mit dem Trick in dieser Antwort durchlaufen werden: Ist es möglich, die nicht aufzählbaren geerbten Eigenschaftsnamen eines Objekts abzurufen? , aber dies wird nicht von dh <9 unterstützt. Das zweite Problem könnte gelöst werden, indem diese Zeile in der Stapelverfolgung herausgerissen wird, aber ich bin mir nicht sicher, wie ich das sicher tun soll (vielleicht nur die zweite Zeile von e.stack.toString () entfernen ??).
Das Snippet zeigt alles.
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)
}
}
Ich würde einen Schritt zurücktreten und überlegen, warum Sie das tun wollen. Ich denke, es geht darum, mit verschiedenen Fehlern unterschiedlich umzugehen.
In Python können Sie beispielsweise die catch-Anweisung auf nur catch beschränken MyValidationError
, und möglicherweise möchten Sie in Javascript etwas Ähnliches tun können.
catch (MyValidationError e) {
....
}
Sie können dies nicht in Javascript tun. Es wird nur einen Fangblock geben. Sie sollten eine if-Anweisung für den Fehler verwenden, um seinen Typ zu bestimmen.
catch(e) {
if(isMyValidationError(e)) {
...
} else {
// maybe rethrow?
throw e;
}
}
Ich denke, ich würde stattdessen ein Rohobjekt mit einem Typ, einer Nachricht und anderen Eigenschaften werfen, die Sie für richtig halten.
throw { type: "validation", message: "Invalid timestamp" }
Und wenn Sie den Fehler bemerken:
catch(e) {
if(e.type === "validation") {
// handle error
}
// re-throw, or whatever else
}
error.stack
, Standardwerkzeuge funktionieren nicht damit usw. usw. Ein besserer Weg wäre, einer Fehlerinstanz Eigenschaften hinzuzufügen, z. B.var e = new Error(); e.type = "validation"; ...
Dies basiert auf der Antwort von George Bailey , erweitert und vereinfacht jedoch die ursprüngliche Idee. Es ist in CoffeeScript geschrieben, lässt sich aber leicht in JavaScript konvertieren. Die Idee ist, Baileys benutzerdefinierten Fehler um einen Dekorateur zu erweitern, der ihn umschließt, sodass Sie problemlos neue benutzerdefinierte Fehler erstellen können.
Hinweis: Dies funktioniert nur in V8. Error.captureStackTrace
In anderen Umgebungen gibt es keine Unterstützung .
Der Dekorateur nimmt einen Namen für den Fehlertyp und gibt eine Funktion zurück, die eine Fehlermeldung entgegennimmt und den Fehlernamen einschließt.
CoreError = (@message) ->
@constructor.prototype.__proto__ = Error.prototype
Error.captureStackTrace @, @constructor
@name = @constructor.name
BaseError = (type) ->
(message) -> new CoreError "#{ type }Error: #{ message }"
Jetzt ist es einfach, neue Fehlertypen zu erstellen.
StorageError = BaseError "Storage"
SignatureError = BaseError "Signature"
Zum Spaß können Sie jetzt eine Funktion definieren, die a auslöst, SignatureError
wenn sie mit zu vielen Argumenten aufgerufen wird.
f = -> throw SignatureError "too many args" if arguments.length
Dies wurde ziemlich gut getestet und scheint auf V8 perfekt zu funktionieren, wobei Traceback, Position usw. beibehalten werden.
Hinweis: Die Verwendung new
ist optional, wenn ein benutzerdefinierter Fehler erstellt wird.
Wenn Sie sich nicht für die Leistung bei Fehlern interessieren, ist dies die kleinste, die Sie tun können
Object.setPrototypeOf(MyError.prototype, Error.prototype)
function MyError(message) {
const error = new Error(message)
Object.setPrototypeOf(error, MyError.prototype);
return error
}
Sie können es ohne neuen nur MyError (Nachricht) verwenden
Durch Ändern des Prototyps nach dem Aufrufen des Konstruktorfehlers müssen Callstack und Nachricht nicht festgelegt werden
Mohsen hat oben in ES6 eine großartige Antwort, die den Namen festlegt, aber wenn Sie TypeScript verwenden oder in der Zukunft leben, in der dieser Vorschlag für öffentliche und private Klassenfelder hoffentlich als Vorschlag über Stufe 3 hinausgegangen ist und ihn gemacht hat Wenn Sie als Teil von ECMAScript / JavaScript in Stufe 4 eintreten, möchten Sie vielleicht wissen, dass dies nur ein bisschen kürzer ist. In Phase 3 beginnen Browser mit der Implementierung von Funktionen. Wenn Ihr Browser dies unterstützt, funktioniert der folgende Code möglicherweise. (Getestet im neuen Edge-Browser v81 scheint es gut zu funktionieren). Seien Sie gewarnt, obwohl dies im Moment eine instabile Funktion ist und vorsichtig verwendet werden sollte. Sie sollten die Browserunterstützung immer auf instabile Funktionen überprüfen. Dieser Beitrag richtet sich hauptsächlich an zukünftige Bewohner, die dies möglicherweise von Browsern unterstützen. Um den Support zu überprüfen, überprüfen Sie MDNundKann ich verwenden . Derzeit wird der Browser-Markt zu 66% unterstützt, was jedoch nicht so gut ist. Wenn Sie ihn jetzt wirklich verwenden möchten und nicht warten möchten, verwenden Sie entweder einen Transpiler wie Babel oder etwas wie TypeScript .
class EOFError extends Error {
name="EOFError"
}
throw new EOFError("Oops errored");
Vergleichen Sie dies mit einem namenlosen Fehler, der beim Auslösen seinen Namen nicht protokolliert.
class NamelessEOFError extends Error {}
throw new NamelessEOFError("Oops errored");
this.name = 'MyError'
Außenseite der Funktion verschieben und in ändernMyError.prototype.name = 'MyError'
.