Was ist ein guter Weg, um Fehler in JavaScript zu erweitern?


369

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:


218

Das einzige Standardfeld, über das das Fehlerobjekt verfügt, ist die messageEigenschaft. (Siehe MDN oder EcmaScript-Sprachspezifikation, Abschnitt 15.11.) Alles andere ist plattformspezifisch.

Mosts Umgebungen setzen Sie die stackEigenschaft, aber fileNameund lineNumbersind 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 stackEigenschaft, aber die throwSchlüsselwortsätze sourceURLund lineEigenschaften des Objekts, das ausgelöst wird. Diese Dinge sind garantiert richtig.

Von mir verwendete Testfälle finden Sie hier: JavaScript-selbst erstellter Fehlerobjektvergleich .


19
Sie können die this.name = 'MyError' Außenseite der Funktion verschieben und in ändern MyError.prototype.name = 'MyError'.
Daniel Beardsley

36
Dies ist hier die einzig richtige Antwort, obwohl ich sie aus Stilgründen wahrscheinlich so schreiben würde. function MyError(message) { this.message = message; this.stack = Error().stack; } MyError.prototype = Object.create(Error.prototype); MyError.prototype.name = "MyError";
Kybernetikos

11
Ich würde auch hinzufügen MyError.prototype.constructor = MyError.
Bharat Khatri

3
in ES6 Error.call (diese Nachricht); sollte initialisieren this, oder?
4esn0k

4
MyError.prototype = Object.create (Error.prototype);
Robin wie der Vogel

170

In ES6:

class MyError extends Error {
  constructor(message) {
    super(message);
    this.name = 'MyError';
  }
}

Quelle


53
Erwähnenswert ist, dass dies nicht funktioniert, wenn Sie ES6-Funktionen über einen Transpiler wie Babel verwenden, da Unterklassen eine Klasse erweitern müssen.
aaaidan

6
Wenn Sie babel verwenden und sich auf Knoten> 5.x befinden, sollten Sie die Voreinstellung es2015 nicht verwenden, aber npmjs.com/package/babel-preset-node5 würde es Ihnen ermöglichen, native es6-Erweiterungen und mehr zu verwenden
Ace

2
Dies ist der beste Weg, wenn möglich. Benutzerdefinierte Fehler verhalten sich eher wie normale Fehler in Chrome und Firefox (und wahrscheinlich auch in anderen Browsern).
Matt Browne

2
Beachten Sie bei Browsern, dass Sie zur Laufzeit die Unterstützung für Klassen erkennen und entsprechend auf eine Nicht-Klassenversion zurückgreifen können. Erkennungscode:var supportsClasses = false; try {eval('class X{}'); supportsClasses = true;} catch (e) {}
Matt Browne

31
Verwenden Sie this.name = this.constructor.name;stattdessen die Wartung , um die Wartung zu vereinfachen .
2онстантин Ван

53

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, DOModerError .

Das Problem wird hier beschrieben:

Was ist mit den anderen SO-Antworten?

Alle gegebenen Antworten beheben das instanceofProblem, 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 instanceofProblem, 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

1
class CustomError extends Error { /* ... */}behandelt herstellerspezifische Argumente ( lineNumberusw.) nicht korrekt. 'Fehler in Javascript mit ES6-Syntax erweitern' ist Babel-spezifisch, wird von Ihrer ES5-Lösung verwendet constund es werden keine benutzerdefinierten Argumente verarbeitet.
Indolering

Sehr vollständige Antwort.
Daniele Orlando

Dies bietet tatsächlich die umfassendste Lösung und erklärt, warum die verschiedenen Teile notwendig sind. Vielen Dank JBE!
AndrewSouthpaw

Dies half mir bei der Lösung des Problems mit dem Erben von "Fehler". Es war ein zweistündiger Albtraum!
Iulian Pinzaru

2
Es ist erwähnenswert, dass das Problem mit console.log(new CustomError('test') instanceof CustomError);// falsezum 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.
Nobita

44

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.prototypein der Funktion, um den gesamten Code zusammenzuhalten. Sie können aber auch ersetzen this.constructormit UserErrorund 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.stackbeginnen, 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)
  • toStringhö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 UserErrorein betrachtet , es instanceof Errorsei denn, Sie führen die folgenden einige Zeit vor Ihnen austhrow UserError

    UserError.prototype = Error.prototype

16
Ich glaube nicht, dass Firefox tatsächlich CaptureStackTrace hat. Es ist eine V8-Erweiterung und für mich in Firefox nicht definiert. Ich kann im Web auch keine Verweise auf Firefox finden, die sie unterstützen. (Danke aber!)
Geoff

5
Error.call(this)tut in der Tat nichts, da es einen Fehler zurückgibt , anstatt ihn zu ändern this.
Kybernetikos

1
Funktioniert hervorragend für Node.js
Rudolf Meijering

1
UserError.prototype = Error.prototypeist irreführend. Dies führt keine Vererbung durch, dies macht sie zur gleichen Klasse .
Halcyon

1
Ich glaube, Object.setPrototypeOf(this.constructor.prototype, Error.prototype)wird this.constructor.prototype.__proto__ = Error.prototypezumindest für aktuelle Browser bevorzugt .
ChrisV

29

Um das Boilerplate für jede Art von Fehler zu vermeiden , habe ich die Weisheit einiger Lösungen zu einer  createErrorTypeFunktion 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';
});

Gibt es einen Grund, warum Sie die Leitung noch benötigen this.name = name;?
Peter Tseng

@PeterTseng Da nameder Prototyp bereits eingestellt ist, ist er nicht mehr erforderlich. Ich habe es entfernt. Vielen Dank!
Ruben Verborgh

27

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.


1
Warum benutzt du setPrototypeOf()? Zumindest laut MDN wird generell davon abgeraten, es zu verwenden, wenn Sie dasselbe erreichen können, indem Sie einfach die .prototypeEigenschaft für den Konstruktor elsefestlegen (wie Sie es im Block für Browser tun, die keine haben setPrototypeOf).
Matt Browne

Das Ändern des Prototyps eines Objekts wird insgesamt nicht empfohlen 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.
Onur Yıldırım

Mein Punkt war, dass ich nicht glaube, dass Sie den Prototyp hier tatsächlich ändern müssen. Sie können einfach Ihre Zeile unten verwenden ( 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/…
Matt Browne

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.
Onur Yıldırım

1
Ich habe eine Übersicht erstellt, die zeigt, warum die Verwendung Object.setPrototypeOfhier 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).
Matt Browne

19

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 inheritsdes utilModuls ( 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 captureStackTraceMethode, die eine stackEigenschaft für das Zielobjekt erstellt (in diesem Fall die CustomErrorInstanziierung). Weitere Informationen zur Funktionsweise finden Sie in der Dokumentation hier .


1
this.message = this.message;Ist das falsch oder gibt es noch verrückte Dinge, die ich über JS nicht weiß?
Alex

1
Hey @Alex, du hast vollkommen recht! Es ist jetzt behoben. Vielen Dank!
Victor Schröder

18

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.


Habe ich es richtig verstanden - wenn Sie keine Unterklasse erstellen und den neuen Fehler (...) nicht direkt verwenden, werden der Dateiname und die Zeile ordnungsgemäß gemeldet? Und Sie sagen im Grunde, dass es keinen Sinn hat, Fehler in der Praxis (echt und nicht nur sexy oder dekorativ) zu klassifizieren?
Jayarjo

6
Diese Antwort ist etwas verwirrend, da Crescent Fresh'ssie gelöscht wurde!
Peter

ist das noch so developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Zeilennummer ist 2, nicht wo neu genannt wurde
Muhammad Umer

13

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();
};

1
Ich habe diese Lösung als npm-Paket veröffentlicht: npmjs.com/package/throwable
JoWie

Unglaublich elegante Lösung, danke fürs Teilen! Eine Variation: new MyErr (arg1, arg2, new Error())und im MyErr-Konstruktor verwenden wir Object.assign, um die Eigenschaften des letzten Args zuzuweisenthis
Peeyush Kushwaha

Ich mag das. Sie umgehen eine Einschränkung, indem Sie die Kapselung anstelle der Vererbung verwenden.
Jenny O'Reilly

13

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;
  }
}

10

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;
};

1
Wie in meiner Antwort dokumentiert, kann diese Lösung ein Problem in Firefox oder anderen Browsern verursachen, die nur die erste Zeile des Stack-Trace in der Konsole protokollieren
Jonathan Benn

Dies ist die einzige Antwort, die ich gefunden habe und die mit ES5 gut funktioniert (die Verwendung von ES6-Klassen funktioniert auch gut). Fehler werden in Chromium DevTools viel besser angezeigt als andere Antworten.
Ben Gubler

Hinweis: Wenn Sie diese Lösung mit TypeScript verwenden, müssen Sie throw CustomError('err')anstelle vonthrow new CustomError('err')
Ben Gubler

8

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';

5

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);
}

3

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)

5
Überschreibt dies nicht die Eigenschaft name für ALL Error-Instanzen?
Panzi

@panzi du bist richtig. Ich habe meinen kleinen Fehler behoben. Danke für die Warnung!
Gautham C.

3

Meine 2 Cent:

Warum noch eine Antwort?

a) Weil der Zugriff auf die Error.stackImmobilie (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/

Was tut es?

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.

Offensichtliches Problem:

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.


2

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.


Dies schlägt in dem Fall fehlm = new InvalidInputError(); dontThrowMeYet(m);
Eric

@ Eric Ich stimme zu, aber dies scheint eine ziemlich kleine Einschränkung zu sein. Ich musste noch nie ein Ausnahmeobjekt im Voraus instanziieren (mit Ausnahme von Metaprogrammierungsanwendungen wie meinem obigen Codebeispiel). Ist das wirklich ein Problem für Sie?
Jonathan Benn

Ja, das Verhalten scheint dasselbe zu sein, daher werde ich meine Antwort ändern. Ich bin nicht 100% zufrieden mit dem Stack-Trace, der Sie zur Zeile "var error" in Firefox und Chrome bringt
Jonathan Benn

1
@ JonathanBenn Ich bin sehr spät zur Party, also hast du das vielleicht schon aufgegriffen. Ich instanziiere häufig ein Ausnahmeobjekt, wenn ich asynchrone Programmierung und Versprechen verwende. Im Anschluss an @ Erics Namen, oft benutze ich m = new ...dann Promise.reject(m). Es ist keine Notwendigkeit, aber der Code ist leichter zu lesen.
BaldEagle

1
@ JonathanBenn: (er er) Am 14. Oktober schien es selten zu sein, ein Ausnahmeobjekt vor dem Werfen zu instanziieren. Ich habe ein Beispiel gegeben, wie ich es einmal gemacht habe. Ich werde nicht sagen, dass es üblich ist, aber es ist praktisch zu haben, wenn ich es will. Und mein Code ist besser lesbar, da sich die Instanziierung in einer Zeile und die Ablehnung in einer anderen Zeile befindet. Ich hoffe das macht es!
BaldEagle

2

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 ErrorMoment 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.


1

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

  • andere Eigenschaften als stackund messagesind nicht in MyErrorund enthalten
  • Der Stacktrace hat eine zusätzliche Zeile, die nicht wirklich notwendig ist.

Das 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 ??).


Ich habe ein Modul erstellt, das die meisten normalen alten Javascript-Objekte, einschließlich Fehler, erweitern kann. Es ist zu diesem Zeitpunkt ziemlich ausgereift. Github.com/fresheneesz/proto
BT

1

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)
      }
    }

0

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
}

1
Ein Objekt zu werfen ist keine gute Idee. Sie haben keine 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"; ...
timruffles

0

Benutzerdefinierter Fehlerdekorateur

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.captureStackTraceIn anderen Umgebungen gibt es keine Unterstützung .

Definieren

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 }"

Verwenden

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, SignatureErrorwenn 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 newist optional, wenn ein benutzerdefinierter Fehler erstellt wird.


0

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


0

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");

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.