validacion - ¿Cuál es una buena manera de extender el error en JavaScript?
validar formulario javascript html5 (19)
Quiero incluir algunas cosas en mi código JS y quiero que sean un error de ejemplo, pero también quiero que sean otra cosa.
En Python, típicamente, una subclase de excepción.
¿Qué es lo apropiado para hacer en JS?
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 envuelva, permitiéndole crear nuevos errores personalizados fácilmente.
Nota: Esto solo funcionará en V8. No hay soporte para Error.captureStackTrace
en 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 simple crear nuevos tipos de error.
StorageError = BaseError "Storage"
SignatureError = BaseError "Signature"
Por diversión, ahora podría definir una función que lanza un SignatureError
si 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 new
es opcional al construir un error personalizado.
¿Qué tal esta solución?
En lugar de lanzar su error personalizado usando:
throw new MyError("Oops!");
Envolvería el objeto Error (como un decorador):
throw new MyError(Error("Oops!"));
Esto asegura que todos los atributos sean correctos, como la pila, el nombre de archivo lineNumber, etc.
Todo lo que tienes que hacer entonces es copiar sobre 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();
};
Como algunas personas han dicho, es bastante fácil con ES6:
class CustomError extends Error { }
Así que intenté eso dentro de mi aplicación, (Angular, Typescript) y simplemente no funcionó. Después de algún tiempo he encontrado que el problema proviene de Typescript: O
Consulte https://github.com/Microsoft/TypeScript/issues/13965
Es muy perturbador 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 se mostrará: Custom error
Intente ejecutarlo con Typescript en su proyecto en el área de juegos de Typescript, se mostrará un 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;
}
}
Como se señaló en la respuesta de Mohsen, en ES6 es posible extender los 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 compatibilidad con los navegadores pre-ES6. Vea a continuación algunas notas sobre cómo se podría implementar, pero mientras tanto sugiero un enfoque relativamente simple que incorpora 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 el soporte para las clases de ES6 con try {eval(''class X{}'')
, pero obtendrá un error de sintaxis si intenta incluir la versión de ES6 en un script cargado por navegadores más antiguos. Por lo tanto, la única forma de admitir todos los navegadores sería cargar un script por separado de forma dinámica (por ejemplo, a través de AJAX o eval()
) para los navegadores que admiten ES6. Otra complicación 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.
Por ahora, ya sea el primer enfoque anterior o simplemente usar Error
directamente sin intentar extenderlo, parece ser lo mejor que se puede hacer en la práctica para el código que debe ser compatible con navegadores que no sean ES6.
Hay otro enfoque que algunas personas podrían querer 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 parezca y se comporte más como un error nativo en la consola (Gracias a la respuesta de Ben por la recomendación). Esta es mi opinión sobre este enfoque: https://gist.github.com/mbrowne/fe45db61cea7858d11be933a998926a8 . Pero dado que un día podremos usar ES6, personalmente no estoy seguro de que la complejidad de ese enfoque valga la pena.
Dado que las excepciones de JavaScript son difíciles de subclasificar, yo no subclasifico. 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 lanzar como un error regular y funcionará como se esperaba, 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 lo llevará a donde se crea el nuevo Error y no a donde lo lanza. 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.
En 2018 , creo que esta es la mejor manera; que soporta IE9 + y navegadores modernos.
ACTUALIZACIÓN : vea esta prueba y repo para comparación en 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 propiedad __proto__
está en deprecated que se usa ampliamente en otras respuestas.
En ES6:
class MyError extends Error {
constructor(message) {
super(message);
this.name = ''MyError'';
}
}
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 la función integrada. util
módulo util
( 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 una instancia 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 de código, la traza 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 método captureStackTrace
, que crea una propiedad de stack
en el objeto de destino (en este caso, se está CustomError
una instancia de CustomError
). Para más detalles sobre cómo funciona, consulte la documentación here .
En el ejemplo anterior, Error.apply
(también Error.call
) no hace nada por mí (Firefox 3.6 / Chrome 5). Una solución que utilizo 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'';
La forma de hacer esto correctamente es devolver el resultado de la aplicación desde el constructor, así como configurar el prototipo de la manera javascripty complicada habitual:
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 punto (lo he repetido un poco) son que
- Las propiedades distintas de la
stack
y elmessage
no se incluyen enMyError
y - El stacktrace tiene una línea adicional que no es realmente necesaria.
El primer problema podría solucionarse mediante la iteración de todas las propiedades de error no enumerables usando el truco de esta respuesta: ¿Es posible obtener los nombres de propiedades heredadas no enumerables de un objeto? , pero esto no es compatible por ejemplo <9. El segundo problema podría resolverse eliminando esa línea en el seguimiento de pila, pero no estoy seguro de cómo hacerlo de forma segura (tal vez simplemente eliminando la segunda línea de e.stack.toString () ??).
La respuesta altamente votada de Crescent Fresh es engañosa. Aunque sus advertencias son inválidas, hay otras limitaciones que él no aborda.
Primero, el razonamiento en el párrafo "Advertencias:" de Crescent no tiene sentido. La explicación implica que la codificación "un montón de if (error instanceof MyError) else ..." es de alguna manera onerosa o detallada en comparación con las múltiples declaraciones catch. Múltiples instrucciones de instancia en un solo bloque catch son tan concisas como múltiples declaraciones catch: código limpio y conciso sin trucos. Esta es una excelente manera de emular el excelente manejo de errores específico de subtipo lanzable de Java.
WRT "aparece la propiedad de mensaje de la subclase que no se establece", que no es el caso si utiliza una subclase de error correctamente construida. Para crear su propia subclase ErrorX Error, simplemente copie el bloque de código que comienza con "var MyError =", cambiando la palabra "MyError" por "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 implementaciones de JavaScript o depuradores que rastrean e informan sobre el seguimiento de la pila y la ubicación de instanciación, 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 utiliza un error directo, sería la ubicación donde ejecutó "error nuevo (...)"). 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 al elemento # 1 para encontrar la ubicación real de creación de instancias.
Me gustaría dar un paso atrás y considerar por qué quieres hacer eso? Creo que el punto es lidiar con diferentes errores de manera diferente.
Por ejemplo, en Python, puede restringir la instrucción catch para que solo se capture MyValidationError
, y quizás desee poder hacer algo similar en javascript.
catch (MyValidationError e) {
....
}
No puedes hacer esto en javascript. Solo va a haber un bloque de captura. Se supone que debes usar una sentencia if en el error para determinar su tipo.
catch(e) { if(isMyValidationError(e)) { ... } else { // maybe rethrow? throw e; } }
Creo que, en cambio, lanzaría un objeto en bruto con un tipo, mensaje y cualquier otra propiedad que considere adecuada.
throw { type: "validation", message: "Invalid timestamp" }
Y cuando atrapes el error:
catch(e) {
if(e.type === "validation") {
// handle error
}
// re-throw, or whatever else
}
Mi solución es más simple que las otras respuestas proporcionadas y no tiene los inconvenientes.
Conserva la cadena de prototipos de error y todas las propiedades de Error sin necesidad de conocerlos de forma específica. Se ha 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;
};
Mis 2 centavos:
¿Por qué otra respuesta?
a) Porque acceder a la propiedad Error.stack
(como en algunas respuestas) tiene una gran penalización de rendimiento.
b) Porque es una sola línea.
c) Porque la solución en https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error no parece conservar 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 MyError.prototype.__proto__
, por lo que está configurando el __proto__
PARA TODAS LAS INSTANCIAS de MyError a un error específico recién creado. Mantiene las propiedades y los métodos de la clase MyError y también coloca las nuevas propiedades de error (incluido .stack) en la cadena __proto__
.
Problema obvio:
No puede tener más de una instancia de MyError con información de pila útil.
No utilice esta solución si no entiende completamente lo que hace this.__proto__.__proto__=
.
Para evitar la repetición de cada error diferente, combiné la sabiduría de algunas de las soluciones en una función createErrorType
:
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;
}
Entonces 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'';
});
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 de nombre del prototipo de la clase de error personalizada en la propiedad de 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 parece a esto (lo probé perezosamente en las herramientas chrome dev):
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)
Edición: por 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 pila donde el soporte está allí.
Edición: Hice esta Comunidad Wiki para permitir más edición.
La solución para V8 (Chrome / Node.JS) funciona en Firefox y puede modificarse para funcionar correctamente en IE. (ver final del post)
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
}
Post 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.prototype
dentro de la función para mantener todo el código unido. Pero también puede reemplazar this.constructor
con UserError
y eso le permite mover el código fuera de la función, por lo que solo se llama una vez.
Si sigue 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.stack
para empezar, así que "no es mi culpa".Error.captureStackTrace(this, this.constructor)
no existe, por lo que debe hacer otra cosa comoif(Error.captureStackTrace) // AKA if not IE Error.captureStackTrace(this, this.constructor)
toString
deja de existir cuando se subclaseError
. Así que también hay que añadir.else this.toString = function () { return this.name + '': '' + this.message }
IE no considerará que
UserError
sea unainstanceof Error
menos que ejecute lo siguiente algún tiempo antes dethrow UserError
UserError.prototype = Error.prototype
En breve:
Si está utilizando ES6 sin transpilers :
class CustomError extends Error { /* ... */}
Si está utilizando el transpiler 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 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 extender ninguna clase incorporada de JavaScript como Date
, Array
, DOM
o Error
.
El problema se describe aquí:
- Native extiende saltos HTMLELement, Array, y otros
- un objeto de La clase que se extiende por tipo de base como Array, Número, Objeto, Cadena o Error no es una instancia de esta clase
¿Qué pasa con las otras respuestas de SO?
Todas las respuestas dadas solucionan el problema de instanceof
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 usa el método mencionado anteriormente, no solo soluciona el problema de instanceof
, 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
El único campo estándar que tiene el objeto Error es la propiedad del message
. (Consulte MDN , o Especificación del lenguaje EcmaScript, sección 15.11) Todo lo demás es específico de la plataforma.
Los entornos de Mosts establecen la propiedad de stack
, pero fileName
y lineNumber
son prácticamente inútiles para ser usados 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
Podría oler la pila, quitarle elementos no deseados y extraer información como fileName y lineNumber, pero hacerlo requiere información sobre la plataforma en la que se está ejecutando JavaScript. La mayoría de los casos son innecesarios, y puede hacerlo en autopsia si realmente lo desea.
Safari es una notable excepción. No hay propiedad de stack
, pero la palabra clave throw
establece las propiedades sourceURL
y line
del objeto que se está lanzando. Esas cosas están garantizadas para ser correctas.
Los casos de prueba que utilicé se pueden encontrar aquí: JavaScript hecho por sí mismo . Comparación de objetos de error .