Cuando desarrollamos algo, a menudo necesitamos nuestras propias clases de error para reflejar cosas específicas que pueden salir mal en nuestras tareas. Para errores en operaciones de red podemos necesitar HttpError
, para operaciones de base de datos DbError
, para operaciones de búsqueda NotFoundError
y así sucesivamente.
Nuestros errores deben apoyar error básico propiedades como message
, name
y, preferentemente, en el stack
. Pero también pueden tener otras propiedades propias, por ej., HttpError
objetos pueden tener un statusCode
propiedad con un valor como 404
o 403
o 500
.
JavaScript permite usar throw
con cualquier argumento, por lo que técnicamente nuestras clases de error personalizadas no necesitan heredar de Error
. Pero si heredamos, entonces es posible usar obj instanceof Error
para identificar objetos de error. Así que es mejor heredar de ella.
a medida que la aplicación crece, nuestros propios errores forman naturalmente una jerarquía., Por ejemplo, HttpTimeoutError
puede heredar de HttpError
, y así sucesivamente.
error de extensión
como ejemplo, consideremos una función readUser(json)
que debería leer JSON con datos de usuario.
he Aquí un ejemplo de cómo un válido json
puede buscar:
let json = `{ "name": "John", "age": 30 }`;
Internamente, vamos a utilizar la etiqueta JSON.parse
. Si recibe un malformado json
, entonces lanza SyntaxError
., Pero incluso si json
es sintácticamente correcto, eso no significa que sea un usuario válido, ¿verdad? Puede perder Los datos necesarios. Por ejemplo, puede que no tenga propiedades name
y age
que sean esenciales para nuestros usuarios.
Nuestra función readUser(json)
no solo leerá JSON, sino que verificará («validará») los datos. Si no hay campos obligatorios, o el formato es incorrecto, entonces eso es un error. Y eso no es un SyntaxError
, porque los datos son sintácticamente correctos, sino otro tipo de error., Lo llamaremos ValidationError
y crearemos una clase para él. Un error de ese tipo también debe llevar la información sobre el campo infractor.
Nuestra clase ValidationError
debe heredar de la clase incorporada Error
.
esa clase está incorporada, pero aquí está su código aproximado para que podamos entender lo que estamos extendiendo:
ahora heredemos ValidationError
de ella y pruébelo en acción:
tenga en cuenta: en la línea (1)
llamamos al constructor padre., JavaScript requiere que llamemos a super
en el constructor hijo, por lo que es obligatorio. El constructor padre establece la propiedad message
.
El constructor padre también establece el name
propiedad "Error"
, por lo que en la línea (2)
nosotros para restablecer el valor correcto.,
Vamos a tratar de usarlo en el readUser(json)
:
El try..catch
bloque en el código anterior se encarga tanto de nuestro ValidationError
y la SyntaxError
de JSON.parse
.
eche un vistazo a cómo usamos instanceof
para verificar el tipo de error específico en la línea (*)
.,
también podríamos mirar err.name
, como este:
// ...// instead of (err instanceof SyntaxError)} else if (err.name == "SyntaxError") { // (*)// ...
El instanceof
versión es mucho mejor, porque en el futuro vamos a extender ValidationError
, hacer subtipos, como lo es el PropertyRequiredError
. Y instanceof
check continuará funcionando para las nuevas clases herederas. Así que eso es a prueba de futuro.
también es importante que si catch
encuentra un error desconocido, entonces lo vuelve a escribir en la línea (**)
., El bloque catch
solo sabe cómo manejar errores de validación y sintaxis, otros tipos (debido a un error tipográfico en el código u otros desconocidos) deberían caer.
más herencia
la clase ValidationError
es muy genérica. Muchas cosas pueden salir mal. La propiedad puede estar ausente o puede estar en un formato incorrecto (como un valor de cadena para age
). Vamos a hacer una clase más concreta PropertyRequiredError
, exactamente para las propiedades ausentes. Llevará información adicional sobre la propiedad que falta.,
La nueva clase PropertyRequiredError
es fácil de usar: sólo tenemos que pasar el nombre de la propiedad: new PropertyRequiredError(property)
. El constructor genera message
legible por humanos.
tenga en cuenta que this.name
in PropertyRequiredError
constructor se asigna de nuevo manualmente. Eso puede volverse un poco tedioso: asignar this.name = <class name>
en cada clase de error personalizada. Podemos evitarlo creando nuestra propia clase de «error básico»que asigna this.name = this.constructor.name
. Y luego heredar todos nuestros errores personalizados de ella.,
vamos a llamarlo MyError
.
Aquí está el código con MyError
y otras clases de error personalizadas, simplificadas:
ahora los errores personalizados son mucho más cortos, especialmente ValidationError
, ya que nos deshicimos de la línea "this.name = ..."
en el constructor.
Wrapping exceptions
el propósito de la función readUser
en el código anterior es «leer los datos del usuario». Puede haber diferentes tipos de errores en el proceso., Ahora mismo tenemos SyntaxError
y ValidationError
, pero en el futuro readUser
función puede crecer y probablemente generar otros tipos de errores.
el código que llama a readUser
debería manejar estos errores. En este momento utiliza múltiples if
s en el bloque catch
, que comprueban la clase y manejan los errores conocidos y repiten los desconocidos.
El esquema es este:
En el código anterior podemos ver dos tipos de errores, pero puede haber más.,
si la función readUser
genera varios tipos de errores, entonces debemos preguntarnos: ¿realmente queremos verificar todos los tipos de error uno por uno cada vez?
a menudo la respuesta es «No» :nos gustaría ser «un nivel por encima de todo eso». Solo queremos saber si hubo un «error de lectura de datos» – por qué exactamente sucedió a menudo es irrelevante (el mensaje de error lo describe). O, mejor aún, nos gustaría tener una manera de obtener los detalles del error, pero solo si es necesario.
la técnica que describimos aquí se llama «wrapping exceptions».,
- haremos una nueva clase
ReadError
para representar un error genérico de «lectura de datos». - La función
readUser
capturará los errores de lectura de datos que se produzcan dentro de ella, comoValidationError
ySyntaxError
, y generará unReadError
en su lugar. - El objeto
ReadError
mantendrá la referencia al error original en su propiedadcause
.,
entonces el código que llama a readUser
solo tendrá que comprobar ReadError
, no para todo tipo de errores de lectura de datos. Y si necesita más detalles de un error, puede verificar su propiedad cause
.,
Aquí está el código que define ReadError
y demuestra su uso en readUser
y try..catch
:
en el código anterior, readUser
funciona exactamente como se describe – detecta errores de sintaxis y validación y lanza errores ReadError
en su lugar (los errores desconocidos se repiten como de costumbre).
así que el código externo comprueba instanceof ReadError
y eso es todo. No es necesario enumerar todos los posibles tipos de error.,
el enfoque se llama «envolviendo excepciones», porque tomamos excepciones de» bajo nivel «y las» envolvemos»en ReadError
que es más abstracto. Es ampliamente utilizado en la programación orientada a objetos.
Summary
- normalmente podemos heredar de
Error
y otras clases de error integradas. Solo tenemos que encargarnos de la propiedadname
y no olvidemos llamar asuper
. - podemos usar
instanceof
para comprobar errores particulares. También funciona con la herencia., Pero a veces tenemos un objeto de error que proviene de una biblioteca de terceros y no hay una manera fácil de obtener su clase. Entoncesname
se puede usar la propiedad para tales comprobaciones. - Wrapping exceptions es una técnica extendida: una función maneja excepciones de bajo nivel y crea errores de alto nivel en lugar de varios de bajo nivel. Las excepciones de bajo nivel a veces se convierten en Propiedades de ese objeto como
err.cause
en los ejemplos anteriores, pero eso no es estrictamente necesario.