¿Cuál es una buena forma de extender Error en JavaScript?
Quiero incluir algunas cosas en mi código JS y quiero que sean una instancia de Error, pero también quiero que sean otra cosa.
En Python, normalmente, se subclasificaría Exception.
¿Qué es lo apropiado que se debe hacer en JS?
En ES6:
class MyError extends Error {
constructor(message) {
super(message);
this.name = 'MyError';
}
}
fuente
El único campo estándar que tiene el objeto Error es la message
propiedad. (Consulte MDN , o Especificación del lenguaje EcmaScript, sección 15.11). Todo lo demás es específico de la plataforma.
La mayoría de los entornos establecen la stack
propiedad, pero fileName
y lineNumber
son prácticamente inútiles para usarse 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 olfatear la pila, eliminar elementos no deseados de ella y extraer información como nombre de archivo y número de línea, pero hacerlo requiere información sobre la plataforma en la que se ejecuta JavaScript actualmente. En la mayoría de los casos esto es innecesario, y puedes hacerlo post-mortem si realmente lo deseas.
Safari es una excepción notable. No hay ninguna stack
propiedad, sino los throw
conjuntos de palabras clave sourceURL
y line
las propiedades del objeto que se está lanzando. Se garantiza que esas cosas serán correctas.
Los casos de prueba que utilicé se pueden encontrar aquí: Comparación de objetos de error propios de JavaScript .
En breve:
Si está utilizando ES6 sin transpiladores :
class CustomError extends Error { /* ... */}
Si está utilizando el transpilador de Babel :
Opción 1: usar babel-plugin-transform-builtin-extend
Opción 2: hazlo tú 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: utilizar el marco clastrofóbico
Explicación:
¿Por qué es un problema ampliar la clase Error usando ES6 y Babel?
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, según la documentación oficial de Babel, no puede ampliar ninguna clase de JavaScript integrada como Date
, Array
o .DOM
Error
El problema se describe aquí:
- Las extensiones nativas rompen HTMLELement, Array y otros
- un objeto de la clase que se extiende por tipo base como Matriz, Número, Objeto, Cadena o Error no es una instancia de esta clase
¿Qué pasa con las otras respuestas SO?
Todas las respuestas dadas solucionan el instanceof
problema pero se pierde el error habitual 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 utiliza el método mencionado anteriormente, no solo soluciona el instanceof
problema sino que también mantiene el error habitual console.log
:
console.log(new CustomError('test'));
// output:
// Error: test
// at CustomError (<anonymous>:2:32)
// at <anonymous>:1:5