Error de extensión en Javascript con sintaxis ES6 y Babel

132

Estoy tratando de extender Error con ES6 y Babel. No esta funcionando.

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

El objeto Error nunca obtiene el conjunto de mensajes correcto.

Prueba en Babel REPL .

Ahora he visto algunas soluciones en SO ( por ejemplo aquí ), pero todas parecen muy poco ES6-y. ¿Cómo hacerlo de una manera agradable, ES6? (Eso está funcionando en Babel)

Karel Bílek
fuente
2
Seguir su enlace a Babel REPL parece indicar que ahora funciona correctamente. Supongo que fue un error en Babel que desde entonces se ha solucionado.
Kybernetikos

Respuestas:

188

Basado en la respuesta de Karel Bílek, haría un pequeño cambio en 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);

Esto se imprimirá MyErroren la pila, y no en el genérico Error.

También agregará el mensaje de error al seguimiento de la pila, que faltaba en el ejemplo de Karel.

También lo usará captureStackTracesi está disponible.

Con Babel 6, necesita transform-builtin-extend ( npm ) para que esto funcione.

Lee Benson
fuente
1
@MichaelYounkin if (typeof Error.captureStackTrace === 'function') { Error.captureStackTrace(this, this.constructor.name) } else { this.stack = (new Error(message)).stack; } . Yo diría que es mejor usar esta función si está disponible, ya que proporciona una pila de llamadas más 'nativa' e imprime el nombre del objeto de error. Por supuesto, si está usando esto únicamente en el lado del servidor también (Nodo), entonces tampoco es un problema.
Lee Benson
44
@MichaelYounkin No creo que esto merezca un voto negativo. El OP habló sobre la extensión de errores en ES6. Siguiendo esa lógica, casi todo el ES6 falta en al menos un navegador. Mi solución (con la verificación de func añadida) proporciona cobertura nativa en el navegador más utilizado, respaldo en todos los demás y 100% de cobertura en Node.js. Estoy de acuerdo en que si el nombre de clase de error consistentemente this.stack = (new Error(message)).stackte da eso ... pero en la práctica, esto probablemente no sea un gran problema.
Lee Benson el
66
Esto no funciona en Babel 6:new MyError('foo') instanceof MyError === false
Sukima
55
Este código precompilado con babel como módulo NPM: extendable-error-class npmjs.com/package/extendable-error-class que es conveniente para evitar una dependencia de babel-plugin-transform-builtin-extend
brillante
3
this.message = message;es redundante consuper(message);
mathieug
39

Combinando esta respuesta , esta respuesta y este código , he creado esta pequeña clase "auxiliar", que parece funcionar bien.

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

Prueba en REPL

Karel Bílek
fuente
1
this.stack = (new Error(message)).stack;- de lo contrario, falta el mensaje en el stacktrace
Lee Benson
3
Sospecho que esto no funciona según sea necesario, porque si lo hace: console.log (myerror instanceof ExtendableError); todavía dice falso ..
Mauno Vähä
44
mismo problema, el uso de instanceof CustomError no funciona, ¿cuál es el punto que se extiende si no puede usar instanceof?
gre
Se puede mejorar agregando el messageconstructor de la pila Error, por lo que muestra el mensaje correcto en la parte superior de la pila cuando se lanza:this.stack = (new Error(message)).stack;
Sebastien
1
myerror.nameahora devuelve "Error". No estoy seguro de si esto está relacionado con versiones posteriores de babel. Vea la respuesta de @ sukima a continuación
Eric H.
27

Para finalmente poner esto a descansar. En Babel 6 es explícita que los desarrolladores no soportan que se extiende desde incorporada. A pesar de que este truco no ayuda con cosas como Map, Set, etc. que hace el trabajo para Error. Esto es importante ya que una de las ideas centrales de un lenguaje que puede generar una excepción es permitir errores personalizados. Esto es doblemente importante ya que las Promesas se vuelven más útiles, ya que están diseñadas para rechazar un Error .

La triste verdad es que aún necesita realizar esto a la antigua usanza en ES2015.

Ejemplo en REPL de Babel

Patrón de error personalizado

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

Por otro lado, hay un complemento para Babel 6 para permitir esto.

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

Actualización: (a partir del 29/09/2016) Después de algunas pruebas, parece que babel.io no tiene en cuenta correctamente todas las afirmaciones (que se extienden desde un error extendido personalizado). Pero en Ember.JS extender Error funciona como se esperaba: https://ember-twiddle.com/d88555a6f408174df0a4c8e0fd6b27ce

Sukima
fuente
sí, con el complemento de babel vinculado funciona correctamente con la respuesta aceptada. (Sin embargo, el archivo resultante no funciona en Node, porque aparentemente no tiene Reflect)
Karel Bílek
Como curiosidad, si las especificaciones de ES2016 dicen que las incorporaciones son extensibles, ¿por qué vms como v8 y el es5 de Babel se trasladan tan en contra? ¿No es una expectativa razonable que una clase pueda extender una clase de la misma manera que una cadena de prototipos puede provenir de otros prototipos? ¿Por qué la necesidad de tal ceramonía oculta en un complemento?
Sukima
Esto es especialmente frustrante cuando la mayoría de los casos de uso solo quieren hacer objetos simples que compartan el comportamiento. Un error personalizado que puede usar Error.toString(). La necesidad de hacer aros y giros especiales para lograr esto significa que la mayoría de los desarrolladores lo evitarán y recurrirán a malas prácticas como tirar cadenas en lugar de errores. O haciendo su propio mapa como objetos. ¿Por qué la necesidad de disuadir tales métodos OOP?
Sukima
En mi opinión, no están en contra, es solo un problema técnico. ¡Aunque no estoy seguro! Puedes preguntarles :) los proyectos son bastante abiertos
Karel Bílek
Hasta ahora, todas las respuestas de preguntas similares sobre el tema se dejan en "babel no apoya eso". Supuse que ese era el final de la conversación. Mi problema es que la falta de apoyo hace que un idioma común de OOP sea difícil e incluso tuve que pelear con compañeros de trabajo para superar la ruina. Solo desearía que aquí hubiera una solución alternativa limpia. Parece que agregar un complemento es la mejor opción entonces.
Sukima
15

Editar : Rompiendo cambios en Typecript 2.1

Es posible que las extensiones integradas como Error, Array y Mapa ya no funcionen.

Como recomendación, puede ajustar manualmente el prototipo inmediatamente después de cualquier super (...) llamada.

Editar la respuesta original de Lee Benson funciona un poco para mí. Esto también agrega stacky métodos adicionales de ExtendableErrorclase a la instancia.

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
fuente
1
También debe llamar Object.setPrototypeOfal MyErrorconstructor. stackoverflow.com/a/41102306/186334 github.com/Microsoft/TypeScript-wiki/blob/master/…
CallMeLaNN
10

Con los últimos cambios en babel 6, encuentro que transform-builtin-extend ya no funciona. Terminé usando este enfoque mixto:

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;

y

import MyError from './MyError';

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

Como resultado, todas estas pruebas pasan:

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
fuente
6

Citando

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

No hay necesidad de this.stack = (new Error()).stack;truco gracias a la super()llamada.

Aunque los códigos anteriores no puede dar salida a menos que el seguimiento de la pila this.stack = (new Error()).stack;o Error.captureStackTrace(this, this.constructor.name);se invoca en Babel . OMI, quizás sea un problema aquí.

En realidad, el seguimiento de la pila se puede generar debajo Chrome consoley Node.js v4.2.1con estos fragmentos de código.

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

Salida de 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)

Salida de 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
fuente
4

Además de la respuesta @zangw, puede definir sus errores de esta manera:

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

que arrojará el nombre, el mensaje y el stacktrace correctos:

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
fuente
44
Esto no funciona: new MyError('foo') instanceof MyError === false.
Sukima
1
Lo hace, en Node.js v7.7.3.
Gunar Gessner
2

Estoy tratando de extender Error con ES6

Esa class MyError extends Error {…}sintaxis es correcta.

Tenga en cuenta que los transpiladores todavía tienen problemas para heredar de objetos integrados. En tu caso,

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

Parece solucionar el problema.

Bergi
fuente
¡Cierto! Pero el mensaje no está configurado de todos modos; escribiré un nuevo ejemplo.
Karel Bílek
He reescrito el ejemplo ahora
Karel Bílek
No funciona para mí
Karel Bílek
El "super (m)" devolverá un objeto vacío, aparentemente. Entonces Object.assign no ayuda.
Karel Bílek
@ KarelBílek: ¿Qué navegador estás usando? Error.call()me devuelve una nueva instancia de error.
Bergi
2

Dado esto, la respuesta aceptada ya no funciona, siempre se puede usar una fábrica como 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);

Melbourne2991
fuente
La respuesta aceptada todavía funciona para mí, si tiene los complementos necesarios de babel. ¡Gracias por esta respuesta también!
Karel Bílek
2

Prefiero una sintaxis más fuerte que la descrita anteriormente. Los métodos adicionales en el tipo de error te ayudarán a crear bonito console.logo algo más.

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

Para probar este código, puede ejecutar algo similar:

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

    console.warn(message);
}

Ampliación de CustomErrortipo son bienvenidos. Es posible agregar alguna funcionalidad específica al tipo extendido o anular el existente. Por ejemplo.

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
fuente
1

Como @sukima menciona, no puede extender JS nativo. La pregunta del OP no puede ser respondida.

Similar a la respuesta de Melbourne2991 , utilicé una fábrica, pero seguí la recomendación de MDN para los tipos de error del cliente .

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.
fuente
1

Esto funciona para mi:

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

Sin usar Babel, pero en ES6 simple, lo siguiente parece funcionar bien para mí:

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

Prueba de 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 ...'

Como puede ver, la pila contiene tanto el nombre del error como el mensaje. No estoy seguro de si me falta algo, pero todas las otras respuestas parecen complicar demasiado las cosas.

JHH
fuente
0

Mejoré un poco la solución de @Lee Benson de esta manera:

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 ejemplo de error

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 

Luego, puede agrupar errores mientras tiene especificadores de opciones para decidir qué hacer de manera diferente en algunas de las situaciones específicas de su aplicación

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