Erweiterungsfehler in Javascript mit ES6-Syntax & Babel

132

Ich versuche, Fehler mit ES6 und Babel zu erweitern. Es klappt nicht.

class MyError extends Error {
  constructor(m) {
    super(m);
  }
}

var error = new Error("ll");
var myerror = new MyError("ll");
console.log(error.message) //shows up correctly
console.log(myerror.message) //shows empty string

Das Fehlerobjekt erhält nie den richtigen Nachrichtensatz.

Versuchen Sie es in Babel REPL .

Jetzt habe ich einige Lösungen für SO gesehen ( zum Beispiel hier ), aber alle scheinen sehr un-ES6-y zu sein. Wie geht das auf eine schöne ES6-Art? (Das funktioniert in Babel)

Karel Bílek
quelle
2
Wenn Sie Ihrem Link zu Babel REPL folgen, scheint dies darauf hinzudeuten, dass es jetzt korrekt funktioniert. Ich nehme an, es war ein Fehler in Babel, der inzwischen behoben wurde.
Kybernetikos

Antworten:

188

Basierend auf der Antwort von Karel Bílek würde ich eine kleine Änderung an Folgendem vornehmen constructor:

class ExtendableError extends Error {
  constructor(message) {
    super(message);
    this.name = this.constructor.name;
    if (typeof Error.captureStackTrace === 'function') {
      Error.captureStackTrace(this, this.constructor);
    } else { 
      this.stack = (new Error(message)).stack; 
    }
  }
}    

// now I can extend

class MyError extends ExtendableError {}

var myerror = new MyError("ll");
console.log(myerror.message);
console.log(myerror instanceof Error);
console.log(myerror.name);
console.log(myerror.stack);

Dies wird MyErrorim Stapel gedruckt und nicht im generischen Error.

Außerdem wird die Fehlermeldung zum Stack-Trace hinzugefügt, der in Karels Beispiel fehlte.

Es wird auch verwendet, captureStackTracewenn es verfügbar ist.

Mit Babel 6 benötigen Sie transform-builtin-extens ( npm ), damit dies funktioniert.

Lee Benson
quelle
1
@ MichaelYounkin if (typeof Error.captureStackTrace === 'function') { Error.captureStackTrace(this, this.constructor.name) } else { this.stack = (new Error(message)).stack; } . Ich würde argumentieren, dass es besser ist, diese Funktion zu verwenden, wenn sie verfügbar ist, da sie einen "nativeren" Aufrufstapel bietet und den Namen des Fehlerobjekts druckt. Wenn Sie dies natürlich auch nur auf der Serverseite (Node) verwenden, ist dies natürlich auch kein Problem.
Lee Benson
4
@ Michael Younkin Ich denke nicht, dass dies eine Ablehnung verdient. Das OP sprach über die Erweiterung von Fehlern in ES6. Nach dieser Logik fehlt fast das gesamte ES6 in mindestens einem Browser. Meine Lösung (mit der zusätzlichen Funktionsprüfung) bietet native Abdeckung im am häufigsten verwendeten Browser, Fallback in jedem anderen und 100% Abdeckung in Node.js. Ich bin damit einverstanden, dass wenn Sie welchen Fehlerklassennamen konsistent haben, this.stack = (new Error(message)).stackSie das bekommen ... aber in der Praxis ist dies wahrscheinlich keine große Sache.
Lee Benson
6
Dies funktioniert nicht in Babel 6:new MyError('foo') instanceof MyError === false
Sukima
5
Dieser Code wurde mit babel als NPM-Modul extendable-error-class vorkompiliert : npmjs.com/package/extendable-error-class, was praktisch ist, um eine Abhängigkeit von babel-plugin-transform-builtin-
extens
3
this.message = message;ist überflüssig mitsuper(message);
Mathieug
39

Durch die Kombination dieser Antwort , dieser Antwort und dieses Codes habe ich diese kleine "Helfer" -Klasse erstellt, die anscheinend gut funktioniert.

class ExtendableError extends Error {
  constructor(message) {
    super();
    this.message = message; 
    this.stack = (new Error()).stack;
    this.name = this.constructor.name;
  }
}    

// now I can extend

class MyError extends ExtendableError {
  constructor(m) {   
    super(m);
  }
}

var myerror = new MyError("ll");
console.log(myerror.message);
console.log(myerror instanceof Error);
console.log(myerror.name);
console.log(myerror.stack);

Versuchen Sie es in REPL

Karel Bílek
quelle
1
this.stack = (new Error(message)).stack;- Andernfalls fehlt die Nachricht im Stacktrace
Lee Benson
3
Ich vermute, dass dies nicht nach Bedarf funktioniert, denn wenn Sie dies tun: console.log (myerror instanceof ExtendableError); es sagt immer noch falsch ..
Mauno Vähä
4
Das gleiche Problem: Die Verwendung von instanceof CustomError funktioniert nicht. Was bringt es, wenn Sie instanceof nicht verwenden können?
Gre
Es kann verbessert werden, indem das messageim Fehlerstapelkonstruktor hinzugefügt wird , so dass es die richtige Meldung oben im Stapel anzeigt, wenn es geworfen wird:this.stack = (new Error(message)).stack;
Sebastien
1
myerror.namegibt jetzt "Fehler" zurück. Ich bin mir nicht sicher, ob dies mit späteren Versionen von babel zusammenhängt. Siehe @ sukimas Antwort unten
Eric H.
27

Um dies endlich zur Ruhe zu bringen. In Babel 6 wird ausdrücklich darauf hingewiesen, dass die Entwickler das Erweitern von integrierten Funktionen nicht unterstützen . Obwohl dieser Trick bei Dingen wie usw. nicht hilfreich Mapist Set, funktioniert er Error. Dies ist wichtig, da eine der Kernideen einer Sprache, die eine Ausnahme auslösen kann, darin besteht, benutzerdefinierte Fehler zuzulassen. Dies ist doppelt wichtig, da Versprechen nützlicher werden, da sie einen Fehler ablehnen sollen .

Die traurige Wahrheit ist, dass Sie dies in ES2015 immer noch auf die alte Weise durchführen müssen.

Beispiel in Babel REPL

Benutzerdefiniertes Fehlermuster

class MyError {
  constructor(message) {
    this.name = 'MyError';
    this.message = message;
    this.stack = new Error().stack; // Optional
  }
}
MyError.prototype = Object.create(Error.prototype);

Auf der anderen Seite gibt es ein Plugin für Babel 6, um dies zu ermöglichen.

https://www.npmjs.com/package/babel-plugin-transform-builtin-extend

Update: (Stand: 29.09.2016) Nach einigen Tests scheint es, dass babel.io nicht alle Asserts ordnungsgemäß berücksichtigt (ausgehend von einem benutzerdefinierten erweiterten Fehler). In Ember.JS funktioniert das Erweitern von Error jedoch wie erwartet: https://ember-twiddle.com/d88555a6f408174df0a4c8e0fd6b27ce

Sukima
quelle
Ja, mit dem verknüpften Babel-Plugin funktioniert es korrekt mit der akzeptierten Antwort. (Die resultierende Datei funktioniert jedoch nicht in Node, da sie anscheinend kein Reflect enthält.)
Karel Bílek
Nur als Kuriosität, wenn die ES2016-Spezifikationen besagen, dass Builds erweiterbar sind, warum sind VMS wie V8 und Babels Es5 so dagegen? Ist es nicht eine vernünftige Erwartung, dass eine Klasse eine Klasse auf die gleiche Weise erweitern kann, wie eine Prototypenkette von anderen Prototypen stammen kann? Warum ist die Notwendigkeit einer solchen Keramik in einem Plugin versteckt?
Sukima
Dies ist besonders frustrierend, wenn die meisten Anwendungsfälle nur einfache Objekte erstellen möchten, die ein gemeinsames Verhalten aufweisen. Ein benutzerdefinierter Fehler, der verwendet werden kann Error.toString(). Die Notwendigkeit, spezielle Reifen und Drehungen durchzuführen, um dies zu erreichen, bedeutet, dass die meisten Entwickler dies vermeiden und auf schlechte Praktiken wie das Werfen von Strings anstelle von Fehlern zurückgreifen. Oder ihre eigene Karte wie Objekte erstellen. Warum müssen solche OOP-Methoden abgeschreckt werden?
Sukima
Meiner Meinung nach sind sie nicht dagegen, es ist nur ein technisches Problem. Ich bin mir aber nicht sicher! Sie können sie fragen :) Die Projekte sind ziemlich offen
Karel Bílek
Bisher sind alle Antworten von ähnlichen Fragen zum Thema bei "babel unterstützt das nicht" geblieben. Ich dachte, das wäre das Ende des Gesprächs. Mein Rindfleisch ist der Mangel an Unterstützung, der eine übliche OOP-Sprache schwierig macht, und ich musste sogar mit Co-Wokern kämpfen, um sie über die Cruft auf der Kesselplatte zu bringen. Ich wünschte nur, hier wäre eine saubere alternative Problemumgehung. Das Hinzufügen eines Plugins scheint dann die beste Wahl zu sein.
Sukima
15

Bearbeiten : Änderungen in Typescript 2.1 brechen

Das Erweitern von integrierten Funktionen wie Fehler, Array und Map funktioniert möglicherweise nicht mehr.

Als Empfehlung können Sie den Prototyp sofort nach Superaufrufen (...) manuell anpassen.

Das Bearbeiten der ursprünglichen Antwort von Lee Benson funktioniert für mich ein wenig. Dadurch werden der Instanz auch stackzusätzliche ExtendableErrorKlassenmethoden hinzugefügt.

class ExtendableError extends Error {
   constructor(message) {
       super(message);
       Object.setPrototypeOf(this, ExtendableError.prototype);
       this.name = this.constructor.name;
   }
   
   dump() {
       return { message: this.message, stack: this.stack }
   }
 }    

class MyError extends ExtendableError {
    constructor(message) {
        super(message);
        Object.setPrototypeOf(this, MyError.prototype);
    }
}

var myerror = new MyError("ll");
console.log(myerror.message);
console.log(myerror.dump());
console.log(myerror instanceof Error);
console.log(myerror.name);
console.log(myerror.stack);
Artur Aleksanyan
quelle
1
Sie müssen auch Object.setPrototypeOfden MyErrorKonstruktor aufrufen . stackoverflow.com/a/41102306/186334 github.com/Microsoft/TypeScript-wiki/blob/master/…
CallMeLaNN
10

Mit den neuesten Änderungen in Babel 6 finde ich, dass Transform-Built-Extend nicht mehr funktioniert. Am Ende habe ich diesen gemischten Ansatz gewählt:

export default class MyError {
    constructor (message) {
        this.name = this.constructor.name;
        this.message = message;
        this.stack = (new Error(message)).stack;
    }
}

MyError.prototype = Object.create(Error.prototype);
MyError.prototype.constructor = MyError;

und

import MyError from './MyError';

export default class MyChildError extends MyError {
    constructor (message) {
        super(message);
    }
}

Als Ergebnis bestehen alle diese Tests:

const sut = new MyError('error message');
expect(sut.message).toBe('error message');
expect(sut).toBeInstanceOf(Error);
expect(sut).toBeInstanceOf(MyError);
expect(sut.name).toBe('MyError');
expect(typeof sut.stack).toBe('string');

const sut = new MyChildError('error message');
expect(sut.message).toBe('error message');
expect(sut).toBeInstanceOf(Error);
expect(sut).toBeInstanceOf(MyError);
expect(sut).toBeInstanceOf(MyChildError);
expect(sut.name).toBe('MyChildError');
expect(typeof sut.stack).toBe('string');
Diego Ferri
quelle
6

Zitieren

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

this.stack = (new Error()).stack;Dank super()Anruf ist kein Trick nötig .

Obwohl der oben genannte Codes nicht ausgeben kann der Stack - Trace , wenn nicht this.stack = (new Error()).stack;oder Error.captureStackTrace(this, this.constructor.name);in aufgerufen Babel . IMO, es ist vielleicht ein Problem hier.

Tatsächlich kann der Stack-Trace unter Chrome consoleund Node.js v4.2.1mit diesen Code-Snippets ausgegeben werden .

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

var myerr = new MyError("test");
console.log(myerr.stack);
console.log(myerr);

Ausgabe von Chrome console.

MyError: test
    at MyError (<anonymous>:3:28)
    at <anonymous>:12:19
    at Object.InjectedScript._evaluateOn (<anonymous>:875:140)
    at Object.InjectedScript._evaluateAndWrap (<anonymous>:808:34)
    at Object.InjectedScript.evaluate (<anonymous>:664:21)

Ausgabe von Node.js

MyError: test
    at MyError (/home/bsadmin/test/test.js:5:8)
    at Object.<anonymous> (/home/bsadmin/test/test.js:11:13)
    at Module._compile (module.js:435:26)
    at Object.Module._extensions..js (module.js:442:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:311:12)
    at Function.Module.runMain (module.js:467:10)
    at startup (node.js:134:18)
    at node.js:961:3
zangw
quelle
4

Zusätzlich zur @ zangw-Antwort können Sie Ihre Fehler folgendermaßen definieren:

'use strict';

class UserError extends Error {
  constructor(msg) {
    super(msg);
    this.name = this.constructor.name;
  }
}

// define errors
class MyError extends UserError {}
class MyOtherError extends UserError {}

console.log(new MyError instanceof Error); // true

throw new MyError('My message');

das wirft korrekten Namen, Nachricht und Stacktrace:

MyError: My message
    at UserError (/Users/honzicek/Projects/api/temp.js:5:10)
    at MyError (/Users/honzicek/Projects/api/temp.js:10:1)
    at Object.<anonymous> (/Users/honzicek/Projects/api/temp.js:14:7)
    at Module._compile (module.js:434:26)
    at Object.Module._extensions..js (module.js:452:10)
    at Module.load (module.js:355:32)
    at Function.Module._load (module.js:310:12)
    at Function.Module.runMain (module.js:475:10)
    at startup (node.js:117:18)
    at node.js:951:3
Honza Stepanovsky
quelle
4
Das funktioniert nicht : new MyError('foo') instanceof MyError === false.
Sukima
1
Es tut auf Node.js v7.7.3.
Gunar Gessner
2

Ich versuche, Fehler mit ES6 zu erweitern

Das class MyError extends Error {…} Syntax ist korrekt.

Beachten Sie, dass Transpiler immer noch Probleme beim Erben von integrierten Objekten haben. In deinem Fall,

var err = super(m);
Object.assign(this, err);

scheint das Problem zu beheben.

Bergi
quelle
Wahr! Aber die Nachricht ist sowieso nicht gesetzt - ich werde ein neues Beispiel schreiben.
Karel Bílek
Ich habe das Beispiel jetzt umgeschrieben
Karel Bílek
Das "super (m)" gibt anscheinend ein leeres Objekt zurück. Object.assign hilft also nicht.
Karel Bílek
@ KarelBílek: Welchen Browser benutzt du? Error.call()gibt eine neue Fehlerinstanz für mich zurück.
Bergi
2

In Anbetracht dessen, dass die akzeptierte Antwort nicht mehr funktioniert, können Sie immer eine Fabrik als Alternative verwenden ( Repl ):

function ErrorFactory(name) {
   return class AppError extends Error {
    constructor(message) {
      super(message);
      this.name = name;
      this.message = message; 
      if (typeof Error.captureStackTrace === 'function') {
        Error.captureStackTrace(this, this.constructor);
      } else { 
        this.stack = (new Error(message)).stack; 
      }
    }
  }     
}

// now I can extend
const MyError = ErrorFactory("MyError");


var myerror = new MyError("ll");
console.log(myerror.message);
console.log(myerror instanceof Error);
console.log(myerror.name);
console.log(myerror.stack);

Melbourne2991
quelle
Die akzeptierte Antwort funktioniert immer noch für mich, wenn Sie die erforderlichen Babel-Plugins haben. Danke auch für diese Antwort!
Karel Bílek
2

Ich bevorzuge eine stärkere Syntax als oben beschrieben. Zusätzliche Methoden bei Fehlertyp helfen Ihnen dabei, hübsche console.logoder etwas anderes zu erstellen .

export class CustomError extends Error {
    /**
     * @param {string} message
     * @param {number} [code = 0]
     */
    constructor(message, code = 0) {
        super();

        /**
         * @type {string}
         * @readonly
         */
        this.message = message;

        /**
         * @type {number}
         * @readonly
         */
        this.code = code;

        /**
         * @type {string}
         * @readonly
         */
        this.name = this.constructor.name;

        /**
         * @type {string}
         * @readonly
         */
        this.stack = CustomError.createStack(this);
    }

    /**
     * @return {string}
     */
    toString() {
        return this.getPrettyMessage();
    }

    /**
     * @return {string}
     */
    getPrettyMessage() {
        return `${this.message} Code: ${this.code}.`;
    }

    /**
     * @param {CustomError} error
     * @return {string}
     * @private
     */
    static createStack(error) {
        return typeof Error.captureStackTrace === 'function'
            ? Error.captureStackTrace(error, error.constructor)
            : (new Error()).stack;
    }
}

Um diesen Code zu testen, können Sie etwas Ähnliches ausführen:

try {
    throw new CustomError('Custom error was thrown!');
} catch (e) {
    const message = e.getPrettyMessage();

    console.warn(message);
}

Eine Erweiterung des CustomErrorTyps ist willkommen. Es ist möglich, dem erweiterten Typ bestimmte Funktionen hinzuzufügen oder vorhandene zu überschreiben. Beispielsweise.

export class RequestError extends CustomError {
    /**
     * @param {string} message
     * @param {string} requestUrl
     * @param {number} [code = 0]
     */
    constructor(message, requestUrl, code = 0) {
        super(message, code);

        /**
         * @type {string}
         * @readonly
         */
        this.requestUrl = requestUrl;
    }

    /**
     * @return {string}
     */
    getPrettyMessage() {
        const base = super.getPrettyMessage();

        return `${base} Request URL: ${this.requestUrl}.`;
    }
}
B. Bohdan
quelle
1

Wie @sukima erwähnt, können Sie native JS nicht erweitern. Die Frage des OP kann nicht beantwortet werden.

Ähnlich wie bei der Antwort von Melbourne2991 habe ich eher eine Fabrik verwendet, aber die Empfehlung von MDN für Kundenfehlertypen befolgt .

function extendError(className){
  function CustomError(message){
    this.name = className;
    this.message = message;
    this.stack = new Error().stack; // Optional
  }
  CustomError.prototype = Object.create(Error.prototype);
  CustomError.prototype.constructor = CustomError;
  return CustomError;
}
Eric H.
quelle
1

Das funktioniert bei mir:

/**
 * @class AuthorizationError
 * @extends {Error}
 */
export class AuthorizationError extends Error {
    message = 'UNAUTHORIZED';
    name = 'AuthorizationError';
}
Michael Liquori
quelle
0

Ohne Babel, aber in einfachem ES6, scheint Folgendes für mich gut zu funktionieren:

class CustomError extends Error {
    constructor(...args) {
        super(...args);
        this.name = this.constructor.name;
    }
}

Testen von REPL:

> const ce = new CustomError('foobar');
> ce.name
'CustomError'
> ce.message
'foobar'
> ce instanceof CustomError
true
> ce.stack
'CustomError: foobar\n    at CustomError (repl:3:1)\n ...'

Wie Sie sehen können, enthält der Stapel sowohl den Fehlernamen als auch die Meldung. Ich bin mir nicht sicher, ob mir etwas fehlt, aber alle anderen Antworten scheinen die Dinge zu komplizieren.

JHH
quelle
0

Ich habe die Lösung von @Lee Benson folgendermaßen ein wenig verbessert:

extensableError.js

class ExtendableError extends Error {
    constructor(message, errorCode) {
        super(message);
        this.name = this.constructor.name;
        this.errorCode = errorCode
        if (typeof Error.captureStackTrace === 'function') {
            Error.captureStackTrace(this, this.constructor);
        } else {
            this.stack = (new Error(message)).stack;
        }
    }


}

export default ExtendableError

ein Beispiel für einen Fehler

import ExtendableError from './ExtendableError'

const AuthorizationErrors = {
    NOT_AUTHORIZED: 401,
    BAD_PROFILE_TYPE: 402,
    ROLE_NOT_ATTRIBUTED: 403
}

class AuthorizationError extends ExtendableError {
    static errors = AuthorizationErrors 
}

export default AuthorizationError 

Anschließend können Sie Fehler gruppieren, während Sie über Optionsspezifizierer verfügen, um zu entscheiden, was in einigen anwendungsspezifischen Situationen anders zu tun ist

new AuthorizationError ("The user must be a seller to be able to do a discount", AuthorizationError.errors.BAD_PROFILE_TYPE )
Zied Hamdi
quelle