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?
fuente
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?
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.
Ambientes mosts establecen la stack
propiedad, pero fileName
y lineNumber
son 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 stack
propiedad, pero los throw
conjuntos de palabras clave sourceURL
y las line
propiedades 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 .
this.name = 'MyError'
exterior de la función y cambiarla aMyError.prototype.name = 'MyError'
.function MyError(message) { this.message = message; this.stack = Error().stack; } MyError.prototype = Object.create(Error.prototype); MyError.prototype.name = "MyError";
MyError.prototype.constructor = MyError
también agregaría .this
, ¿verdad?En ES6:
fuente
fuente
var supportsClasses = false; try {eval('class X{}'); supportsClasses = true;} catch (e) {}
this.name = this.constructor.name;
en su lugar.En breve:
Si está utilizando ES6 sin transpiladores :
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)
Si está utilizando ES5 puro :
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.
De hecho, a partir de la documentación oficial de Babel, no puede ampliar ninguna clase de JavaScript incorporada. , como
Date
,Array
,DOM
oError
.El problema se describe aquí:
¿Qué pasa con las otras respuestas SO?
Todas las respuestas dadas solucionan el
instanceof
problema, pero pierde el error regularconsole.log
:Mientras que usando el método mencionado anteriormente, no solo soluciona el
instanceof
problema sino que también mantiene el error regularconsole.log
:fuente
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 usaconst
y no maneja argumentos personalizados.console.log(new CustomError('test') instanceof CustomError);// false
era 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.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)
Publicación original en "¡Muéstrame el código!"
Version corta:
Mantengo
this.constructor.prototype.__proto__ = Error.prototype
dentro de la función para mantener todo el código junto. Pero también puede reemplazarthis.constructor
conUserError
y 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.stack
que comenzar, así que "no es mi culpa".Error.captureStackTrace(this, this.constructor)
no existe, por lo que debes hacer otra cosa comotoString
deja de existir cuando subclasesError
. Entonces también necesitas agregar.IE no lo considerará
UserError
como unainstanceof Error
opción a menos que ejecute lo siguiente algún tiempo antes dethrow UserError
fuente
Error.call(this)
de hecho no está haciendo nada, ya que devuelve un error en lugar de modificarthis
.UserError.prototype = Error.prototype
es engañoso. Esto no hace herencia, esto los convierte en la misma clase .Object.setPrototypeOf(this.constructor.prototype, Error.prototype)
es preferiblethis.constructor.prototype.__proto__ = Error.prototype
, al menos para los navegadores actuales.Para evitar la repetición de cada tipo diferente de error, combiné la sabiduría de algunas de las soluciones en una
createErrorType
función:Luego puede definir nuevos tipos de error fácilmente de la siguiente manera:
fuente
this.name = name;
?name
que ya está configurado en el prototipo, ya no es necesario. Lo quité. ¡Gracias!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.
También tenga en cuenta que la
__proto__
propiedad está en desuso, que se usa ampliamente en otras respuestas.fuente
setPrototypeOf()
? Al menos según MDN, generalmente se desaconseja usarlo si puede lograr lo mismo simplemente configurando la.prototype
propiedad en el constructor (como lo está haciendo en elelse
bloque para los navegadores que no tienensetPrototypeOf
).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, úselosetPrototypeOf
.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 deCustomError
. 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/…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.Object.setPrototypeOf
no tiene sentido aquí, al menos no en la forma en que lo está usando: gist.github.com/mbrowne/4af54767dcb3d529648f5a8aa11d6348 . Tal vez quisiste escribirObject.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ónCustomError.prototype
).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
inherits
delutil
mó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 :
Ahora puede crear instancias y pasar / lanzar su
CustomError
: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
captureStackTrace
método, que crea unastack
propiedad en el objeto de destino (en este caso, laCustomError
instanciación). Para obtener más detalles sobre cómo funciona, consulte la documentación aquí .fuente
this.message = this.message;
¿Esto está mal o todavía hay cosas locas que no sé sobre JS?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.
fuente
Crescent Fresh's
se ha eliminado!¿Qué tal esta solución?
En lugar de lanzar su error personalizado usando:
Envolvería el objeto Error (algo así como un Decorador):
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):
fuente
new MyErr (arg1, arg2, new Error())
y en el constructor MyErr usamosObject.assign
para asignar las propiedades delthis
Como han dicho algunas personas, es bastante fácil con ES6:
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:
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:
fuente
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:
Ejemplo de uso:
Para entornos que requieren un polyfil de setPrototypeOf:
fuente
throw CustomError('err')
lugar dethrow new CustomError('err')
En el ejemplo anterior
Error.apply
(tambiénError.call
) no hace nada por mí (Firefox 3.6 / Chrome 5). Una solución alternativa que uso es:fuente
En Nodo, como han dicho otros, es simple:
fuente
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:
Entonces el ejemplo completo sería:
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):
fuente
Mis 2 centavos:
¿Por qué otra respuesta?
a) Porque acceder a la
Error.stack
propiedad (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.
ejemplo de uso
http://jsfiddle.net/luciotato/xXyeB/
¿Qué hace?
this.__proto__.__proto__
es decirMyError.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.fuente
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:
La nueva excepción anterior se puede generar como un error normal y funcionará como se espera, por ejemplo:
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.
fuente
m = new InvalidInputError(); dontThrowMeYet(m);
m = new ...
a continuaciónPromise.reject(m)
. No es necesario, pero el código es más fácil de leer.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:
En ES6 es tan simple como:
... 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 oeval()
) para navegadores que admitan ES6. Una complicación adicional es queeval()
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
Error
directamente 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.fuente
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:
Los únicos problemas con esta forma de hacerlo en este momento (lo he iterado un poco) son que
stack
ymessage
no están incluidas enMyError
yEl 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 () ??).
fuente
El fragmento lo muestra todo.
fuente
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.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.
Y cuando captas el error:
fuente
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"; ...
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.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.
Utilizar
Ahora es sencillo crear nuevos tipos de error.
Por diversión, ahora podría definir una función que arroje un
SignatureError
si se llama con demasiados argumentos.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.fuente
si no te importan las interpretaciones por errores, esto es lo más pequeño que puedes hacer
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
fuente
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 .
Compare esto con un error sin nombre que cuando se lanza no registrará su nombre.
fuente