Estensione dell'errore in Javascript con sintassi ES6 e Babele


132

Sto cercando di estendere Error con ES6 e Babel. Non sta funzionando.

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

L'oggetto Error non ottiene mai il set di messaggi corretto.

Prova in Babel REPL .

Ora ho visto alcune soluzioni su SO ( ad esempio qui ), ma sembrano tutte molto non-ES6-y. Come farlo in un bel modo ES6? (Funziona in Babele)


2
Seguire il tuo link a Babel REPL sembra indicare che ora funziona correttamente. Presumo che da allora sia stato corretto un bug di Babele.
Kybernetikos,

Risposte:


188

Sulla base della risposta di Karel Bílek, farei una piccola modifica a 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);

Questo verrà stampato MyErrorin pila e non nel generico Error.

Aggiungerà anche il messaggio di errore alla traccia dello stack, che mancava nell'esempio di Karel.

Utilizzerà anche captureStackTracese è disponibile.

Con Babel 6, hai bisogno di transform-builtin- extender ( npm ) per farlo funzionare.


1
@MichaelYounkin if (typeof Error.captureStackTrace === 'function') { Error.captureStackTrace(this, this.constructor.name) } else { this.stack = (new Error(message)).stack; } . Direi che è meglio usare questa funzione se è disponibile, poiché fornisce uno stack di chiamate più "nativo" e stampa il nome dell'oggetto errore. Ovviamente, se lo stai utilizzando solo sul lato server (Nodo), non è nemmeno un problema.
Lee Benson,

4
@MichaelYounkin Non penso che questo meriti un voto negativo. L'OP ha parlato dell'estensione degli errori in ES6. Seguendo questa logica, quasi tutto ES6 manca da almeno un browser. La mia soluzione (con il controllo funzionale aggiunto) fornisce copertura nativa nel browser più utilizzato, ripiegamento reciproco e copertura del 100% in Node.js. Sono d'accordo che se tu quale errore classname coerentemente this.stack = (new Error(message)).stackti capita che ... ma in pratica, questo probabilmente non è un grosso problema.
Lee Benson,

6
Questo non funziona in Babele 6:new MyError('foo') instanceof MyError === false
Sukima

5
Questo codice precompilato con babel come modulo NPM: extendable-error-class npmjs.com/package/extendable-error-class che è conveniente per evitare una dipendenza da babel-plugin-transform-builtin-
ext

3
this.message = message;è ridondante consuper(message);
mathieug il

39

Combinando questa risposta , questa risposta e questo codice , ho creato questa piccola classe "helper", che sembra funzionare bene.

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

Prova in REPL


1
this.stack = (new Error(message)).stack;- altrimenti il ​​messaggio non è presente nello stacktrace
Lee Benson,

3
Ho il sospetto che questo non funzioni come necessario, perché se lo fai: console.log (myerror instanceof ExtendableError); dice ancora falso ..
Mauno Vähä,

4
stesso problema, l'utilizzo di instanceof CustomError non funziona, qual è il punto di estensione se non è possibile utilizzare instanceof.
gre

Può essere migliorato aggiungendo messagenel costruttore dello stack Error, quindi mostra il messaggio giusto in cima allo stack quando viene lanciato:this.stack = (new Error(message)).stack;
Sebastien,

1
myerror.nameora restituisce "Errore". Non sono sicuro che ciò sia correlato alle versioni successive di babel. Vedi la risposta di @ sukima in basso
Eric

27

Per mettere finalmente questo a riposo. In Babel 6 è esplicito che gli sviluppatori non supportano estende da costruito in. Anche se questo trucco non sarà di aiuto con le cose come Map, Setecc funziona per Error. Questo è importante in quanto una delle idee fondamentali di una lingua che può generare un'eccezione è consentire errori personalizzati. Ciò è doppiamente importante poiché le promesse diventano più utili poiché sono progettate per rifiutare un errore .

La triste verità è che devi ancora eseguire ciò alla vecchia maniera in ES2015.

Esempio in Babel REPL

Pattern di errore personalizzato

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

D'altra parte c'è un plugin per Babel 6 per permetterlo.

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

Aggiornamento: (dal 29-09-2016) Dopo alcuni test sembra che babel.io non tenga correttamente conto di tutte le affermazioni (estendendosi da un errore esteso personalizzato). Ma in Ember.JS l'estensione dell'errore funziona come previsto: https://ember-twiddle.com/d88555a6f408174df0a4c8e0fd6b27ce


sì, con il plug-in babel collegato funziona correttamente con la risposta accettata. (Tuttavia, il file risultante non funziona in Node, perché a quanto pare non ha Reflect)
Karel Bílek

Proprio come una curiosità, se le specifiche ES2016 affermano che i builtin sono estendibili, perché vms come v8 ed es5 di Babel si traspongono così contro di esso? Non è ragionevole aspettarsi che una classe possa estendere una classe nello stesso modo in cui una catena di prototipi può provenire da altri prototipi? Perché la necessità di tale ceramony nascosta in un plugin?
Sukima,

Ciò è particolarmente frustrante quando la maggior parte dei casi d'uso vuole solo creare oggetti semplici che condividano il comportamento. Un errore personalizzato che può utilizzare Error.toString(). La necessità di fare cerchi speciali e rotazioni per raggiungere questo obiettivo significa che la maggior parte degli sviluppatori lo eviterà e ricorrerà a cattive pratiche come lanciare stringhe invece di errori. O creando la propria mappa come oggetti. Perché la necessità di scoraggiare tali metodi OOP?
Sukima,

Secondo me non sono contrari, è solo un problema tecnico. Non sono sicuro però! Puoi chiedere loro :) i progetti sono abbastanza aperti
Karel Bílek

Finora tutte le risposte a domande semplici sull'argomento sono state lasciate a "Babele non lo sostiene" Ho pensato che fosse la fine della conversazione. La mia carne bovina è la mancanza di supporto che rende difficile un linguaggio OOP comune e ho persino dovuto combattere con i co-woker per farli passare sull'innesto della piastra della caldaia. Vorrei solo che qui fosse una soluzione alternativa pulita. Sembra che l'aggiunta di un plugin sia la scelta migliore allora.
Sukima,

15

Modifica : Ultime modifiche in Typescript 2.1

L'estensione di built-in come Error, Array e Map potrebbe non funzionare più.

Come raccomandazione, è possibile regolare manualmente il prototipo immediatamente dopo eventuali chiamate super (...).

Modificare la risposta originale di Lee Benson funziona un po 'per me. Questo aggiunge anche stacke metodi aggiuntivi di ExtendableErrorclasse all'istanza.

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

1
È necessario chiamare anche Object.setPrototypeOfil MyErrorcostruttore. stackoverflow.com/a/41102306/186334 github.com/Microsoft/TypeScript-wiki/blob/master/...
CallMeLaNN

10

Con le ultime modifiche in babel 6, trovo che transform-builtin-extension non funzioni più. Ho finito per usare questo approccio misto:

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;

e

import MyError from './MyError';

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

Di conseguenza tutti questi test superano:

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

6

citando

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

Non è necessario alcun this.stack = (new Error()).stack;trucco grazie a super()call.

Sebbene i codici sopra riportati non possano generare la traccia dello stack a meno this.stack = (new Error()).stack;che non Error.captureStackTrace(this, this.constructor.name);sia invocato in Babel . IMO, forse un problema qui.

In realtà, la traccia dello stack può essere emessa sotto Chrome consolee Node.js v4.2.1con questo frammenti di codice.

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

Uscita di 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)

Uscita di 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

4

Oltre alla risposta @zangw, puoi definire i tuoi errori in questo modo:

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

che genererà nome, messaggio e stacktrace corretti:

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

4
Questo non funziona: new MyError('foo') instanceof MyError === false.
Sukima,

1
Lo fa, il Node.js v7.7.3.
Gunar Gessner,

2

Sto cercando di estendere l'errore con ES6

Questa class MyError extends Error {…}sintassi è corretta.

Si noti che i transpiler hanno ancora problemi con l'ereditarietà dagli oggetti incorporati. Nel tuo caso,

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

sembra risolvere il problema.


Vero! Ma il messaggio non è impostato comunque - scriverò un nuovo esempio.
Karel Bílek,

Ho riscritto l'esempio ora
Karel Bílek,


Il "super (m)" restituirà un oggetto vuoto, apparentemente. Quindi Object.assign non aiuta.
Karel Bílek,

@ KarelBílek: quale browser stai usando? Error.call()restituisce una nuova istanza di errore per me.
Bergi,

2

Dato questo la risposta accettata non funziona più si può sempre usare una fabbrica in alternativa ( 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);


La risposta accettata funziona ancora per me, se hai i plugin babel necessari. Grazie anche per questa risposta, però!
Karel Bílek,

2

Preferisco una sintassi più forte di quella sopra descritta. Ulteriori metodi al tipo di errore ti aiuteranno a creare console.logqualcosa di carino o qualcos'altro.

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

Per testare questo codice puoi eseguire qualcosa di simile:

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

    console.warn(message);
}

L'estensione del CustomErrortipo è benvenuta. È possibile aggiungere alcune funzionalità specifiche al tipo esteso o sovrascrivere esistenti. Per esempio.

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

1

Come menziona @sukima, non è possibile estendere JS nativo. Non è possibile rispondere alla domanda del PO.

Simile alla risposta di Melbourne2991 , ho usato piuttosto una fabbrica, ma ho seguito la raccomandazione di MDN per i tipi di errore dei clienti .

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

1

Questo funziona per me:

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

0

Non usando Babel, ma in semplice ES6, il seguente sembra funzionare bene per me:

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

Test da 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 ...'

Come puoi vedere, lo stack contiene sia il nome che il messaggio di errore. Non sono sicuro che mi manchi qualcosa, ma tutte le altre risposte sembrano complicare troppo le cose.


0

Ho migliorato un po 'la soluzione di @Lee Benson in questo modo:

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

un esempio di errore

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 

Quindi sei in grado di raggruppare gli errori mentre disponi di identificatori di opzioni per decidere cosa fare diversamente in alcune situazioni specifiche dell'applicazione

new AuthorizationError ("The user must be a seller to be able to do a discount", AuthorizationError.errors.BAD_PROFILE_TYPE )
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.