¿Cuál es una buena manera de extender Error en JavaScript?

369

Quiero incluir algunas cosas en mi código JS y quiero que sean instancias de Error, pero también quiero que sean otra cosa.

En Python, típicamente, se subclasificaría Exception.

¿Qué es lo apropiado para hacer en JS?

Josh Gibson
fuente

Respuestas:

218

El único campo estándar que tiene el objeto Error es la messagepropiedad. (Consulte MDN , o Especificación del lenguaje EcmaScript, sección 15.11) Todo lo demás es específico de la plataforma.

Ambientes mosts establecen la stackpropiedad, pero fileNamey lineNumberson prácticamente inútiles para ser utilizado en herencia.

Entonces, el enfoque minimalista es:

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

Puede oler la pila, quitar elementos no deseados de ella y extraer información como fileName y lineNumber, pero para hacerlo se requiere información sobre la plataforma en la que se ejecuta JavaScript actualmente. La mayoría de los casos es innecesario, y puede hacerlo en la autopsia si realmente lo desea.

Safari es una notable excepción. No hay ninguna stackpropiedad, pero los throwconjuntos de palabras clave sourceURLy las linepropiedades del objeto que se está lanzando. Esas cosas están garantizadas para ser correctas.

Los casos de prueba que utilicé se pueden encontrar aquí: comparación de objetos de error de JavaScript hecho a sí mismo .

Tero
fuente
19
Puede mover el this.name = 'MyError' exterior de la función y cambiarla a MyError.prototype.name = 'MyError'.
Daniel Beardsley
36
Esta es la única respuesta correcta aquí, aunque por cuestiones de estilo, probablemente lo escribiría así. function MyError(message) { this.message = message; this.stack = Error().stack; } MyError.prototype = Object.create(Error.prototype); MyError.prototype.name = "MyError";
Kybernetikos
11
Yo MyError.prototype.constructor = MyErrortambién agregaría .
Bharat Khatri
3
en ES6 Error.call (este, mensaje); debe inicializar this, ¿verdad?
4esn0k
44
MyError.prototype = Object.create (Error.prototype);
Robin como el pájaro
170

En ES6:

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

fuente

Mohsen
fuente
53
Vale la pena mencionar que esto no funciona si está utilizando las funciones de ES6 a través de un transpilador, como Babel, ya que las subclases deben extender una clase.
aaaidan
66
Si está usando babel y está en el nodo> 5.x, no debería usar el preajuste es2015 pero npmjs.com/package/babel-preset-node5 le permitiría usar extensiones nativas de es6 y más
Ace
2
Esta es la mejor manera de hacerlo cuando sea posible. Los errores personalizados se comportan más como errores normales en Chrome y Firefox (y probablemente también en otros navegadores).
Matt Browne
2
Para los navegadores, tenga en cuenta que puede detectar la compatibilidad con las clases en tiempo de ejecución y recurrir a una versión que no sea de clase en consecuencia. Código de detección:var supportsClasses = false; try {eval('class X{}'); supportsClasses = true;} catch (e) {}
Matt Browne
31
Para facilitar el mantenimiento , use this.name = this.constructor.name;en su lugar.
Константин Ван
53

En breve:

  • Si está utilizando ES6 sin transpiladores :

    class CustomError extends Error { /* ... */}
  • Si está utilizando el transpilador Babel :

Opción 1: use babel-plugin-transform-builtin-extend

Opción 2: hágalo usted mismo (inspirado en esa misma biblioteca)

    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);
  • Si está utilizando ES5 puro :

    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;
    }
  • Alternativa: use el marco de Classtrophobic

Explicación:

¿Por qué extender la clase Error usando ES6 y Babel es un problema?

Porque una instancia de CustomError ya no se reconoce como tal.

class CustomError extends Error {}
console.log(new CustomError('test') instanceof Error);// true
console.log(new CustomError('test') instanceof CustomError);// false

De hecho, a partir de la documentación oficial de Babel, no puede ampliar ninguna clase de JavaScript incorporada. , como Date, Array, DOMo Error.

El problema se describe aquí:

¿Qué pasa con las otras respuestas SO?

Todas las respuestas dadas solucionan el instanceofproblema, pero pierde el error regular console.log:

console.log(new CustomError('test'));
// output:
// CustomError {name: "MyError", message: "test", stack: "Error↵    at CustomError (<anonymous>:4:19)↵    at <anonymous>:1:5"}

Mientras que usando el método mencionado anteriormente, no solo soluciona el instanceofproblema sino que también mantiene el error regular console.log:

console.log(new CustomError('test'));
// output:
// Error: test
//     at CustomError (<anonymous>:2:32)
//     at <anonymous>:1:5
JBE
fuente
1
class CustomError extends Error { /* ... */}no maneja correctamente los argumentos específicos del proveedor ( lineNumber, etc.), 'Extending Error in Javascript with ES6 syntax' es específico de Babel, su solución ES5 usa consty no maneja argumentos personalizados.
Invasión
Muy completa respuesta.
Daniele Orlando
En realidad, esto proporciona la solución más completa y explica por qué son necesarias las diversas piezas. Muchas gracias JBE!
AndrewSouthpaw
Esto me ayudó a resolver el problema de heredar de "Error". ¡Fue una pesadilla de dos horas!
Iulian Pinzaru
2
Vale la pena señalar que el problema con console.log(new CustomError('test') instanceof CustomError);// falseera cierto en el momento de la escritura, pero ahora se ha resuelto. De hecho, el problema vinculado en la respuesta se ha resuelto y podemos probar el comportamiento correcto aquí y pegando el código en el REPL y viendo cómo se transpira correctamente para crear una instancia con la cadena de prototipo correcta.
Nobita
44

Editar: favor lea los comentarios. Resulta que esto solo funciona bien en V8 (Chrome / Node.JS). Mi intención era proporcionar una solución de navegador cruzado, que funcionaría en todos los navegadores, y proporcionar un seguimiento de la pila donde hay soporte.

Editar: hice este Wiki de la comunidad para permitir más edición.

La solución para V8 (Chrome / Node.JS), funciona en Firefox y se puede modificar para que funcione principalmente en IE. (ver final de la publicación)

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
}

Publicación original en "¡Muéstrame el código!"

Version corta:

function UserError(message) {
  this.constructor.prototype.__proto__ = Error.prototype
  Error.captureStackTrace(this, this.constructor)
  this.name = this.constructor.name
  this.message = message
}

Mantengo this.constructor.prototype.__proto__ = Error.prototypedentro de la función para mantener todo el código junto. Pero también puede reemplazar this.constructorcon UserErrory eso le permite mover el código fuera de la función, por lo que solo se llama una vez.

Si va por esa ruta, asegúrese de llamar a esa línea antes de la primera vez que lance UserError.

Esa advertencia no aplica la función, porque las funciones se crean primero, sin importar el orden. Por lo tanto, puede mover la función al final del archivo, sin ningún problema.

Compatibilidad del navegador

Funciona en Firefox y Chrome (y Node.JS) y cumple todas las promesas.

Internet Explorer falla en lo siguiente

  • Los errores no tienen err.stackque comenzar, así que "no es mi culpa".

  • Error.captureStackTrace(this, this.constructor) no existe, por lo que debes hacer otra cosa como

    if(Error.captureStackTrace) // AKA if not IE
        Error.captureStackTrace(this, this.constructor)
  • toStringdeja de existir cuando subclases Error. Entonces también necesitas agregar.

    else
        this.toString = function () { return this.name + ': ' + this.message }
  • IE no lo considerará UserErrorcomo una instanceof Erroropción a menos que ejecute lo siguiente algún tiempo antes dethrow UserError

    UserError.prototype = Error.prototype
George Bailey
fuente
16
No creo que Firefox tenga realmente captureStackTrace. Es una extensión V8 y no está definida en Firefox para mí, ni puedo encontrar ninguna referencia en la web a Firefox que lo soporte. (¡Aunque gracias!)
Geoff
55
Error.call(this)de hecho no está haciendo nada, ya que devuelve un error en lugar de modificar this.
kybernetikos
1
Funciona muy bien para Node.js
Rudolf Meijering
1
UserError.prototype = Error.prototypees engañoso. Esto no hace herencia, esto los convierte en la misma clase .
Halcyon el
1
Creo que Object.setPrototypeOf(this.constructor.prototype, Error.prototype)es preferible this.constructor.prototype.__proto__ = Error.prototype, al menos para los navegadores actuales.
ChrisV
29

Para evitar la repetición de cada tipo diferente de error, combiné la sabiduría de algunas de las soluciones en una  createErrorTypefunción:

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

Luego puede definir nuevos tipos de error fácilmente de la siguiente manera:

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';
});
Ruben Verborgh
fuente
¿Hay alguna razón por la que todavía necesitas la línea this.name = name;?
Peter Tseng
@PeterTseng Dado nameque ya está configurado en el prototipo, ya no es necesario. Lo quité. ¡Gracias!
Ruben Verborgh
27

En 2018 , creo que esta es la mejor manera; que admite IE9 + y navegadores modernos.

ACTUALIZACIÓN : Consulte esta prueba y el repositorio para comparar las diferentes implementaciones.

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

También tenga en cuenta que la __proto__propiedad está en desuso, que se usa ampliamente en otras respuestas.

Onur Yıldırım
fuente
1
¿Por qué estás usando setPrototypeOf()? Al menos según MDN, generalmente se desaconseja usarlo si puede lograr lo mismo simplemente configurando la .prototypepropiedad en el constructor (como lo está haciendo en el elsebloque para los navegadores que no tienen setPrototypeOf).
Matt Browne
Cambiar el prototipo de un objeto se desaconseja todos juntos, no setPrototypeOf. Pero si aún lo necesita (como lo pide OP), debe usar la metodología incorporada. Como indica MDN, esta se considera la forma correcta de establecer el prototipo de un objeto. En otras palabras, MDN dice que no cambie el prototipo (ya que afecta el rendimiento y la optimización), pero si es necesario, úselo setPrototypeOf.
Onur Yıldırım
Mi punto era que no creo que realmente necesites cambiar el prototipo aquí. Simplemente puede usar su línea en la parte inferior ( CustomError.prototype = Object.create(Error.prototype)). Además, Object.setPrototypeOf(CustomError, Error.prototype)está configurando el prototipo del propio constructor en lugar de especificar el prototipo para nuevas instancias de CustomError. De todos modos, en 2016 creo que en realidad hay una mejor manera de extender los errores, aunque todavía estoy descubriendo cómo usarlo junto con Babel: github.com/loganfsmyth/babel-plugin-transform-builtin-extend/…
Matt Browne
CustomError.prototype = Object.create(Error.prototype)También está cambiando el prototipo. Debe cambiarlo ya que no hay una lógica de extensión / herencia incorporada en ES5. Estoy seguro de que el complemento de Babel que mencionas hace cosas similares.
Onur Yıldırım
1
Creé una esencia que demuestra por qué el uso Object.setPrototypeOfno tiene sentido aquí, al menos no en la forma en que lo está usando: gist.github.com/mbrowne/4af54767dcb3d529648f5a8aa11d6348 . Tal vez quisiste escribir Object.setPrototypeOf(CustomError.prototype, Error.prototype), eso tendría un poco más de sentido (aunque todavía no proporciona ningún beneficio sobre la configuración CustomError.prototype).
Matt Browne
19

En aras de la integridad, solo porque ninguna de las respuestas anteriores mencionó este método, si está trabajando con Node.js y no tiene que preocuparse por la compatibilidad del navegador, el efecto deseado es bastante fácil de lograr con el incorporado inheritsdel utilmódulo ( documentos oficiales aquí ).

Por ejemplo, supongamos que desea crear una clase de error personalizada que tome un código de error como primer argumento y el mensaje de error como segundo argumento:

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

Ahora puede crear instancias y pasar / lanzar su 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!');

Tenga en cuenta que, con este fragmento, el seguimiento de la pila tendrá el nombre y la línea correctos del archivo, ¡y la instancia de error tendrá el nombre correcto!

Esto sucede debido al uso del captureStackTracemétodo, que crea una stackpropiedad en el objeto de destino (en este caso, la CustomErrorinstanciación). Para obtener más detalles sobre cómo funciona, consulte la documentación aquí .

Victor Schröder
fuente
1
this.message = this.message;¿Esto está mal o todavía hay cosas locas que no sé sobre JS?
Alex
1
Hola @ Alex, tienes toda la razón! Ya está arreglado. ¡Gracias!
Victor Schröder
18

La respuesta de Crescent Fresh, la respuesta altamente votada, es engañosa. Aunque sus advertencias no son válidas, hay otras limitaciones que no aborda.

Primero, el razonamiento en el párrafo "Advertencias:" de Crescent no tiene sentido. La explicación implica que codificar "un montón de if (error instanceof MyError) else ..." es de alguna manera oneroso o detallado en comparación con las múltiples declaraciones catch. Múltiples instancias de declaraciones en un solo bloque catch son tan concisas como múltiples declaraciones catch: código limpio y conciso sin ningún truco. Esta es una excelente manera de emular el excelente manejo de errores específicos de subtipo de lanzamiento de Java.

WRT "aparece la propiedad del mensaje de la subclase no se establece", ese no es el caso si utiliza una subclase de error construida correctamente. Para crear su propia subclase de ErrorX Error, simplemente copie el bloque de código que comienza con "var MyError =", cambiando la palabra "MyError" a "ErrorX". (Si desea agregar métodos personalizados a su subclase, siga el texto de muestra).

La limitación real y significativa de la subclasificación de errores de JavaScript es que para las implementaciones o depuradores de JavaScript que rastrean e informan sobre el seguimiento de la pila y la ubicación de la instancia, como FireFox, una ubicación en su propia implementación de subclase de error se registrará como el punto de instanciación de clase, mientras que si usó un error directo, sería la ubicación donde ejecutó "nuevo error (...)"). Los usuarios de IE probablemente nunca lo notarán, pero los usuarios de Fire Bug en FF verán valores inútiles de nombre de archivo y número de línea junto con estos Errores, y tendrán que profundizar en el seguimiento de la pila hasta el elemento # 1 para encontrar la ubicación de instanciación real.

Blaine
fuente
¿Lo entendí bien: que si no subclasifica y utiliza el nuevo Error (...) directamente, entonces el nombre del archivo y la línea se informan correctamente? Y básicamente dices que en la práctica (real y no solo sexy o decorativa) subclasificar errores, ¿no tiene sentido?
jayarjo
66
¡Esta respuesta es algo confusa ya que Crescent Fresh'sse ha eliminado!
Peter
¿Sigue siendo el caso? developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… El número de línea es 2 no donde se llamó nuevo
Muhammad Umer
13

¿Qué tal esta solución?

En lugar de lanzar su error personalizado usando:

throw new MyError("Oops!");

Envolvería el objeto Error (algo así como un Decorador):

throw new MyError(Error("Oops!"));

Esto asegura que todos los atributos sean correctos, como la pila, fileName lineNumber, etc.

Todo lo que tiene que hacer es copiar los atributos o definir captadores para ellos. Aquí hay un ejemplo usando getters (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();
};
JoWie
fuente
1
He lanzado esta solución como un paquete npm
JoWie
Solución increíblemente elegante, ¡gracias por compartir! Una variación: new MyErr (arg1, arg2, new Error())y en el constructor MyErr usamos Object.assignpara asignar las propiedades del this
último argumento
Me gusta esto. Omite una limitación al usar encapsulación en lugar de herencia.
Jenny O'Reilly
13

Como han dicho algunas personas, es bastante fácil con ES6:

class CustomError extends Error { }

Así que intenté eso dentro de mi aplicación, (Angular, Typecript) y simplemente no funcionó. Después de un tiempo descubrí que el problema proviene de Typecript: O

Ver https://github.com/Microsoft/TypeScript/issues/13965

Es muy inquietante porque si lo haces:

class CustomError extends Error {}


try {
  throw new CustomError()
} catch(e) {
  if (e instanceof CustomError) {
    console.log('Custom error');
  } else {
    console.log('Basic error');
  }
}

En el nodo o directamente en su navegador, mostrará: Custom error

Intente ejecutar eso con TypeScript en su proyecto en el área de juegos de Typecript, se mostrará Basic error ...

La solución es hacer lo siguiente:

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;
  }
}
maxime1992
fuente
10

Mi solución es más simple que las otras respuestas proporcionadas y no tiene las desventajas.

Conserva la cadena de prototipos de Error y todas las propiedades en Error sin necesidad de un conocimiento específico de ellas. Ha sido probado en Chrome, Firefox, Node e IE11.

La única limitación es una entrada adicional en la parte superior de la pila de llamadas. Pero eso es fácilmente ignorado.

Aquí hay un ejemplo con dos parámetros personalizados:

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

Ejemplo de uso:

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
}

Para entornos que requieren un polyfil de setPrototypeOf:

Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
    obj.__proto__ = proto;
    return obj;
};
Ben
fuente
1
Como se documenta en mi respuesta, esta solución puede causar un problema en Firefox u otros navegadores que solo registran la primera línea del seguimiento de la pila en la consola
Jonathan Benn
Esta es la única respuesta que he encontrado que funciona bien con ES5 (el uso de clases de ES6 también funciona bien). Los errores se muestran mucho mejor en Chromium DevTools que otras respuestas.
Ben Gubler
Nota: si está utilizando esta solución con TypeScript, debe ejecutar en throw CustomError('err')lugar dethrow new CustomError('err')
Ben Gubler
8

En el ejemplo anterior Error.apply(también Error.call) no hace nada por mí (Firefox 3.6 / Chrome 5). Una solución alternativa que uso es:

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';
panzi
fuente
5

En Nodo, como han dicho otros, es simple:

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);
}
Muhammad Umer
fuente
3

Solo quiero agregar a lo que otros ya han dicho:

Para asegurarse de que la clase de error personalizada se muestre correctamente en el seguimiento de la pila, debe establecer la propiedad del nombre del prototipo de la clase de error personalizada en la propiedad del nombre de la clase de error personalizada. Esto es lo que quiero decir:

CustomError.prototype = Error.prototype;
CustomError.prototype.name = 'CustomError';

Entonces el ejemplo completo sería:

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

Cuando todo está dicho y hecho, lanzas tu nueva excepción y se ve así (perezosamente intenté esto en las herramientas de desarrollo de Chrome):

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)
Gautham C.
fuente
55
¿No sobrescribe esto la propiedad de nombre para TODAS las instancias de error?
Panzi
@panzi tienes razón. He arreglado mi pequeño error. ¡Gracias por el aviso!
Gautham C.
3

Mis 2 centavos:

¿Por qué otra respuesta?

a) Porque acceder a la Error.stackpropiedad (como en algunas respuestas) tiene una gran penalización de rendimiento.

b) Porque es solo una línea.

c) Porque la solución en https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error no parece preservar la información de la pila.

//MyError class constructor
function MyError(msg){
    this.__proto__.__proto__ = Error.apply(null, arguments);
};

ejemplo de uso

http://jsfiddle.net/luciotato/xXyeB/

¿Qué hace?

this.__proto__.__proto__es decir MyError.prototype.__proto__, está configurando __proto__PARA TODAS LAS INSTANCIAS de MyError en un error específico recién creado. Mantiene las propiedades y métodos de la clase MyError y también coloca las nuevas propiedades de error (incluido .stack) en la __proto__cadena.

Problema obvio:

No puede tener más de una instancia de MyError con información útil de la pila.

No use esta solución si no comprende completamente lo que this.__proto__.__proto__=hace.

Lucio M. Tato
fuente
2

Como las excepciones de JavaScript son difíciles de subclasificar, no lo hago. Acabo de crear una nueva clase de excepción y uso un error dentro de ella. Cambio la propiedad Error.name para que se vea como mi excepción personalizada en la consola:

var InvalidInputError = function(message) {
    var error = new Error(message);
    error.name = 'InvalidInputError';
    return error;
};

La nueva excepción anterior se puede generar como un error normal y funcionará como se espera, por ejemplo:

throw new InvalidInputError("Input must be a string");
// Output: Uncaught InvalidInputError: Input must be a string 

Advertencia: el seguimiento de la pila no es perfecto, ya que te llevará a donde se crea el nuevo Error y no a donde lo arrojas. Esto no es un gran problema en Chrome porque le proporciona un seguimiento completo de la pila directamente en la consola. Pero es más problemático en Firefox, por ejemplo.

Jonathan Benn
fuente
Esto falla en el casom = new InvalidInputError(); dontThrowMeYet(m);
Eric
@Eric Estoy de acuerdo, pero esto parece una limitación bastante pequeña. Nunca he necesitado instanciar un objeto de excepción antes de tiempo (excepto los usos de metaprogramación como mi ejemplo de código anterior). ¿Es esto realmente un problema para ti?
Jonathan Benn
Sí, el comportamiento parece ser el mismo, por lo que cambiaré mi respuesta. No estoy 100% satisfecho con el seguimiento de la pila, que lo lleva a la línea de "error de var" en Firefox y Chrome
Jonathan Benn
1
@ JonathanBenn Llegué muy tarde a la fiesta, así que tal vez ya hayas recogido esto. Frecuentemente instancia un objeto de excepción cuando uso programación asíncrona y Promesas. Tras @ nombres de Eric, que a menudo utilizan m = new ...a continuación Promise.reject(m). No es necesario, pero el código es más fácil de leer.
BaldEagle
1
@JonathanBenn: (he he) el 14 de octubre, parecía pensar que crear una instancia de un objeto de excepción antes de lanzarlo sería raro. Di un ejemplo de una vez que lo hago. No diré que es común, pero es útil tenerlo cuando lo quiero. Y, mi código es más legible porque la creación de instancias es todo en una línea y el rechazo todo en otra. ¡Espero que lo haga!
BaldEagle
2

Como se señaló en la respuesta de Mohsen, en ES6 es posible extender errores usando clases. Es mucho más fácil y su comportamiento es más consistente con los errores nativos ... pero desafortunadamente no es una cuestión simple usar esto en el navegador si necesita soportar navegadores anteriores a ES6. Consulte a continuación algunas notas sobre cómo se podría implementar, pero mientras tanto sugiero un enfoque relativamente simple que incorpore algunas de las mejores sugerencias de otras respuestas:

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

En ES6 es tan simple como:

class CustomError extends Error {}

... y puede detectar la compatibilidad con las clases de ES6 try {eval('class X{}'), pero obtendrá un error de sintaxis si intenta incluir la versión de ES6 en un script cargado por navegadores antiguos. Por lo tanto, la única forma de admitir todos los navegadores sería cargar una secuencia de comandos separada dinámicamente (por ejemplo, a través de AJAX o eval()) para navegadores que admitan ES6. Una complicación adicional es que eval()no se admite en todos los entornos (debido a las Políticas de seguridad de contenido), lo que puede o no ser una consideración para su proyecto.

Entonces, por ahora, ya sea el primer enfoque anterior o simplemente usarlo Errordirectamente sin tratar de extenderlo, parece ser lo mejor que prácticamente se puede hacer para el código que necesita admitir navegadores que no sean ES6.

Hay otro enfoque que algunas personas pueden considerar, que es usar Object.setPrototypeOf()donde esté disponible para crear un objeto de error que sea una instancia de su tipo de error personalizado pero que se vea y se comporte más como un error nativo en la consola (gracias a la respuesta de Ben para la recomendación). Aquí está mi opinión sobre ese enfoque: https://gist.github.com/mbrowne/fe45db61cea7858d11be933a998926a8 . Pero dado que algún día podremos usar ES6, personalmente no estoy seguro de que la complejidad de ese enfoque valga la pena.

Matt Browne
fuente
1

La forma de hacer esto bien es devolver el resultado de la aplicación desde el constructor, así como configurar el prototipo de la manera complicada habitual de javascripty:

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

Los únicos problemas con esta forma de hacerlo en este momento (lo he iterado un poco) son que

  • propiedades distintas stacky messageno están incluidas en MyErrory
  • el stacktrace tiene una línea adicional que no es realmente necesaria.

El primer problema podría solucionarse iterando a través de todas las propiedades de error no enumerables utilizando el truco de esta respuesta: ¿Es posible obtener los nombres de propiedad heredados no enumerables de un objeto? , pero esto no es compatible con ie <9. El segundo problema podría resolverse arrancando esa línea en el seguimiento de la pila, pero no estoy seguro de cómo hacerlo de manera segura (¿tal vez solo quitando la segunda línea de e.stack.toString () ??).

BT
fuente
Creé un módulo que puede extender el objeto javascript antiguo más regular, incluidos los errores. Es bastante maduro en este momento github.com/fresheneesz/proto
BT
1

El fragmento lo muestra todo.

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)
      }
    }
Amarpreet Singh
fuente
0

Daría un paso atrás y consideraría por qué quieres hacer eso. Creo que el punto es tratar los diferentes errores de manera diferente.

Por ejemplo, en Python, puede restringir la instrucción catch a solo catch MyValidationError, y tal vez quiera hacer algo similar en javascript.

catch (MyValidationError e) {
    ....
}

No puedes hacer esto en javascript. Solo habrá un bloque de captura. Se supone que debe usar una declaración if en el error para determinar su tipo.

catch(e) { if(isMyValidationError(e)) { ... } else { // maybe rethrow? throw e; } }

Creo que en su lugar arrojaría un objeto sin formato con un tipo, mensaje y cualquier otra propiedad que considere adecuada.

throw { type: "validation", message: "Invalid timestamp" }

Y cuando captas el error:

catch(e) {
    if(e.type === "validation") {
         // handle error
    }
    // re-throw, or whatever else
}
Hasen
fuente
1
Lanzar un objeto no es una gran idea. No tiene error.stack, las herramientas estándar no funcionarán con él, etc. Una mejor manera sería agregar propiedades a una instancia de error, por ejemplovar e = new Error(); e.type = "validation"; ...
timruffles
0

Decorador de errores personalizado

Esto se basa en la respuesta de George Bailey , pero amplía y simplifica la idea original. Está escrito en CoffeeScript, pero es fácil de convertir a JavaScript. La idea es extender el error personalizado de Bailey con un decorador que lo envuelve, lo que le permite crear nuevos errores personalizados fácilmente.

Nota: Esto solo funcionará en V8. No hay soporte para Error.captureStackTraceen otros entornos.

Definir

El decorador toma un nombre para el tipo de error y devuelve una función que toma un mensaje de error y encierra el nombre del error.

CoreError = (@message) ->

    @constructor.prototype.__proto__ = Error.prototype
    Error.captureStackTrace @, @constructor
    @name = @constructor.name

BaseError = (type) ->

    (message) -> new CoreError "#{ type }Error: #{ message }"

Utilizar

Ahora es sencillo crear nuevos tipos de error.

StorageError   = BaseError "Storage"
SignatureError = BaseError "Signature"

Por diversión, ahora podría definir una función que arroje un SignatureErrorsi se llama con demasiados argumentos.

f = -> throw SignatureError "too many args" if arguments.length

Esto se ha probado bastante bien y parece funcionar perfectamente en V8, manteniendo el rastreo, la posición, etc.

Nota: El uso newes opcional al construir un error personalizado.

Carl Smith
fuente
0

si no te importan las interpretaciones por errores, esto es lo más pequeño que puedes hacer

Object.setPrototypeOf(MyError.prototype, Error.prototype)
function MyError(message) {
    const error = new Error(message)
    Object.setPrototypeOf(error, MyError.prototype);
    return error
}

puedes usarlo sin nuevo solo MyError (mensaje)

Al cambiar el prototipo después de que se llama el error del constructor, no tenemos que establecer la pila de llamadas y el mensaje

bormat
fuente
0

Mohsen tiene una gran respuesta arriba en ES6 que establece el nombre, pero si está utilizando TypeScript o si está viviendo en el futuro, con suerte, esta propuesta para los campos de clase públicos y privados ha pasado la etapa 3 como una propuesta y lo hizo en la etapa 4 como parte de ECMAScript / JavaScript, entonces es posible que desee saber que esto es un poco más corto. La etapa 3 es donde los navegadores comienzan a implementar funciones, por lo que si su navegador lo admite, el siguiente código podría funcionar. (Probado en el nuevo navegador Edge v81 parece funcionar bien). Tenga en cuenta que esta es una característica inestable en este momento y debe usarse con precaución y siempre debe verificar el soporte del navegador en las características inestables. Esta publicación es principalmente para aquellos futuros habitantes cuando los navegadores pueden admitir esto. Para verificar el soporte, consulte MDN y¿Puedo utilizar . Actualmente tiene un 66% de soporte en todo el mercado de navegadores que está llegando allí, pero no es tan bueno, así que si realmente quieres usarlo ahora y no quieres esperar, usa un transpilador como Babel o algo como TypeScript .

class EOFError extends Error { 
  name="EOFError"
}
throw new EOFError("Oops errored");

Compare esto con un error sin nombre que cuando se lanza no registrará su nombre.

class NamelessEOFError extends Error {}
throw new NamelessEOFError("Oops errored");

Juan
fuente