Los errores son buenos. El autor del material, cuya traducción publicamos hoy, dice que confía en que esta idea sea conocida por todos. A primera vista, los errores parecen atemorizantes. Pueden estar acompañados de algún tipo de pérdida. Un error cometido en público perjudica la autoridad de quien lo cometió. Pero al cometer errores, aprendemos de ellos, lo que significa que la próxima vez que nos encontremos en una situación en la que previamente nos comportamos incorrectamente, hacemos todo lo que sea necesario.

Arriba hablamos sobre los errores que cometen las personas en la vida cotidiana. Los errores en la programación son otra cosa. Los mensajes de error nos ayudan a mejorar el código, nos permiten informar a los usuarios de nuestros proyectos que algo salió mal y tal vez decirles a los usuarios cómo comportarse para que ya no se produzcan errores.
Este material de manejo de errores de JavaScript se divide en tres partes. Primero, daremos una descripción general del sistema de manejo de errores en JavaScript y hablaremos sobre los objetos de error. Después de eso, buscamos la respuesta a la pregunta de qué hacer con los errores que ocurren en el código del servidor (en particular, cuando se usa el paquete Node.js + Express.js). A continuación, discutimos el manejo de errores en React.js. Los marcos que se considerarán aquí se seleccionan debido a su enorme popularidad. Sin embargo, los principios para trabajar con los errores discutidos aquí son universales, por lo que incluso si no usa Express y React, puede aplicar fácilmente lo que aprendió a las herramientas con las que trabaja.
El código para el proyecto de demostración utilizado en este material se puede encontrar en
este repositorio.
1. Errores en JavaScript y formas universales de trabajar con ellos.
Si algo salió mal en su código, puede usar la siguiente construcción.
throw new Error('something went wrong')
Durante la ejecución de este comando, se creará una instancia del objeto
Error y se generará una excepción (o, como dicen, "arrojada") con este objeto. La instrucción
throw puede lanzar excepciones que contienen expresiones arbitrarias. En este caso, la ejecución del script se detendrá si no se han tomado medidas para manejar el error.
Los programadores principiantes de JS generalmente no usan la instrucción
throw
. Por lo general, se encuentran con excepciones lanzadas por el tiempo de ejecución del lenguaje o las bibliotecas de terceros. Cuando esto sucede, algo así como un
ReferenceError: fs is not defined
entra en la consola
ReferenceError: fs is not defined
y la ejecución del programa se detiene.
▍ Error de objeto
Las instancias del objeto
Error
tienen varias propiedades que podemos usar. La primera propiedad que nos interesa es el
message
. Aquí es donde se obtiene la línea que se puede pasar al constructor de errores como argumento. Por ejemplo, a continuación se muestra cómo crear una instancia del objeto
Error
y enviar a la consola la cadena que pasó el constructor accediendo a su propiedad de
message
.
const myError = new Error('please improve your code') console.log(myError.message)
La segunda propiedad del objeto, muy importante, es la traza de la pila de errores. Esta es una propiedad de
stack
. En cuanto a él, puede ver la pila de llamadas (historial de errores), que muestra la secuencia de operaciones que condujeron al mal funcionamiento del programa. En particular, esto nos permite comprender qué archivo contiene el código incorrecto y ver qué secuencia de llamadas a funciones condujo al error. Aquí hay un ejemplo de lo que puede ver accediendo a la propiedad de
stack
.
Error: please improve your code at Object.<anonymous> (/Users/gisderdube/Documents/_projects/hacking.nosync/error-handling/src/general.js:1:79) at Module._compile (internal/modules/cjs/loader.js:689:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10) at Module.load (internal/modules/cjs/loader.js:599:32) at tryModuleLoad (internal/modules/cjs/loader.js:538:12) at Function.Module._load (internal/modules/cjs/loader.js:530:3) at Function.Module.runMain (internal/modules/cjs/loader.js:742:12) at startup (internal/bootstrap/node.js:266:19) at bootstrapNodeJSCore (internal/bootstrap/node.js:596:3)
Aquí, en la parte superior, hay un mensaje de error, seguido de una indicación de la sección de código cuya ejecución causó el error, luego describe el lugar desde donde se llamó a esta sección fallida. Esto continúa hasta el "más alejado" en relación con el fragmento de código de error.
▍ Generación y manejo de errores
Crear una instancia del objeto
Error
, es decir, ejecutar un comando de la forma
new Error()
, no conlleva ninguna consecuencia especial. Cosas interesantes comienzan a suceder después de aplicar el operador de
throw
, lo que genera un error. Como ya se mencionó, si dicho error no se procesa, la ejecución del script se detendrá. En este caso, no importa si el programador usó el operador
throw
, si ocurrió un error en cierta biblioteca o en el tiempo de ejecución del idioma (en un navegador o en Node.js). Hablemos de varios escenarios de manejo de errores.
▍construcción intente ... atrapar
El
try...catch
es la forma más fácil de manejar errores que a menudo se olvidan. Hoy en día, sin embargo, se usa mucho más intensamente que antes, debido al hecho de que puede usarse para manejar errores en construcciones
async/await
.
Este bloque se puede usar para manejar cualquier error que ocurra en el código síncrono. Considera un ejemplo.
const a = 5 try { console.log(b)
Si en este ejemplo no incluimos el comando
console.log(b)
fallido en un
try...catch
, el script se detendría.
▍ finalmente bloquear
A veces sucede que algunos códigos deben ejecutarse independientemente de si se produjo un error o no. Para hacer esto, puede usar el tercer bloque opcional,
finally
, en el
try...catch
construct. A menudo, su uso es equivalente a algún código que viene inmediatamente después de
try...catch
, pero en algunas situaciones puede ser útil. Aquí hay un ejemplo de su uso.
const a = 5 try { console.log(b)
▍Mecanismos asincrónicos - devoluciones de llamada
Al programar en JavaScript, siempre debe prestar atención a las secciones de código que se ejecutan de forma asincrónica. Si tiene una función asincrónica y se produce un error, la secuencia de comandos continuará ejecutándose. Cuando se implementan mecanismos asincrónicos en JS utilizando devoluciones de llamada (por cierto, esto no se recomienda), la devolución de llamada correspondiente (función de devolución de llamada) generalmente recibe dos parámetros. Esto es algo así como el parámetro
err
, que puede contener un error y un
result
, con los resultados de una operación asincrónica. Se parece a esto:
myAsyncFunc(someInput, (err, result) => { if(err) return console.error(err)
Si se produce un error en la devolución de llamada, es visible allí como un parámetro de
err
. De lo contrario, este parámetro obtendrá el valor
undefined
o
null
. Si resulta que
err
algo, es importante responder a esto porque en nuestro ejemplo, usando el comando
return
, o usando la
if...else
y colocando comandos en el bloque
else
para trabajar con el resultado de la operación asincrónica. El punto es, en el caso de que ocurra un error, excluir la posibilidad de trabajar con el resultado, el parámetro
result
, que en este caso puede ser
undefined
. Trabajar con este valor, si se supone, por ejemplo, que contiene un objeto, puede causar un error. Digamos que esto sucede cuando intentas usar la construcción
result.data
o
result.data
similar.
▍Mecanismos asincrónicos: promesas
Para realizar operaciones asincrónicas en JavaScript, es mejor usar promesas en lugar de devoluciones de llamada. Aquí, además de mejorar la legibilidad del código, existen mecanismos más avanzados para el manejo de errores. Es decir, no necesita preocuparse por el objeto de error que puede caer en la función de devolución de llamada al usar promesas. Aquí para este propósito se proporciona un bloque de
catch
especial. Intercepta todos los errores que ocurrieron en las promesas anteriores o todos los errores que ocurrieron en el código después del
catch
anterior. Tenga en cuenta que si se produce un error en la promesa que no tiene un bloque
catch
para procesar, esto no detendrá la ejecución del script, pero el mensaje de error no será particularmente legible.
(node:7741) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: something went wrong (node:7741) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. */
Como resultado, siempre puede recomendar, cuando trabaje con promesas, usar el
catch
. Echa un vistazo a un ejemplo.
Promise.resolve(1) .then(res => { console.log(res)
▍Mecanismos asincrónicos e intenta ... atrapar
Después de que apareciera la construcción
async/await
en JavaScript, volvimos a la forma clásica de manejar los errores:
try...catch...finally
. Manejar errores con este enfoque es muy fácil y conveniente. Considera un ejemplo.
;(async function() { try { await someFuncThatThrowsAnError() } catch (err) { console.error(err)
Con este enfoque, los errores en el código asincrónico se manejan de la misma manera que en el síncrono. Como resultado, ahora, si es necesario, en un solo
catch
puede manejar una gama más amplia de errores.
2. Generando y procesando errores en el código del servidor
Ahora que tenemos las herramientas para trabajar con errores, veamos qué podemos hacer con ellos en situaciones reales. Generar y manejar correctamente los errores es un aspecto crítico de la programación del lado del servidor. Existen diferentes enfoques para trabajar con errores. Aquí demostraremos un enfoque utilizando nuestro propio constructor para instancias del objeto
Error
y códigos de error que se pasan convenientemente al front-end o a cualquier mecanismo que use API de servidor. La forma en que se estructura el backend de un proyecto específico realmente no importa, ya que con cualquier enfoque puede usar las mismas ideas para trabajar con errores.
Como el marco del servidor responsable del enrutamiento, utilizaremos Express.js. Pensemos en qué estructura necesitamos para organizar un sistema eficaz de manejo de errores. Entonces, esto es lo que necesitamos:
- El manejo universal de errores es un mecanismo básico adecuado para manejar cualquier error, durante el cual
Something went wrong, please try again or contact us
un mensaje como Something went wrong, please try again or contact us
, pidiéndole al usuario que intente realizar la operación que falló, nuevamente o comuníquese con el propietario del servidor. Este sistema no es particularmente inteligente, pero al menos puede informar al usuario de que algo salió mal. Tal mensaje es mucho mejor que la "descarga sin fin" o algo similar. - Procesamiento de errores específicos: un mecanismo que le permite informar al usuario información detallada sobre las causas del comportamiento incorrecto del sistema y brindarle consejos específicos sobre cómo abordar el problema. Por ejemplo, esto puede referirse a la ausencia de algunos datos importantes en la solicitud que el usuario envía al servidor, o que ya hay un cierto registro en la base de datos que está tratando de agregar nuevamente, y así sucesivamente.
▍ Desarrollando su propio constructor de objetos de error
Aquí usaremos la clase de
Error
estándar y la extenderemos. El uso de mecanismos de herencia en JavaScript es arriesgado, pero en este caso, estos mecanismos son muy útiles. ¿Por qué necesitamos herencia? El hecho es que para que podamos depurar convenientemente el código, necesitamos información sobre el seguimiento de la pila de errores. Extendiendo la clase de
Error
estándar, tenemos la capacidad de rastrear la pila sin esfuerzo adicional. Agregamos dos propiedades a nuestro propio objeto de error. La primera es la propiedad de
code
, a la que se puede acceder utilizando una estructura de la forma
err.code
. El segundo es la propiedad de
status
. Grabará el código de estado HTTP, que se planea transmitir a la parte cliente de la aplicación.
Así es como se ve la clase
CustomError
, cuyo código está diseñado como un módulo.
class CustomError extends Error { constructor(code = 'GENERIC', status = 500, ...params) { super(...params) if (Error.captureStackTrace) { Error.captureStackTrace(this, CustomError) } this.code = code this.status = status } } module.exports = CustomError
▍ Enrutamiento
Ahora que nuestro objeto de error está listo para su uso, necesitamos configurar la estructura de ruta. Como se mencionó anteriormente, necesitamos implementar un enfoque unificado para el manejo de errores, que nos permite manejar igualmente los errores para todas las rutas. Por defecto, el framework Express.js no es totalmente compatible con dicho esquema. El hecho es que todas sus rutas están encapsuladas.
Para hacer frente a este problema, podemos implementar nuestro propio controlador de ruta y definir la lógica de ruta en forma de funciones ordinarias. Gracias a este enfoque, si la función de ruta (o cualquier otra función) arroja un error, caerá en el controlador de ruta, que luego puede pasarlo a la parte cliente de la aplicación. Si se produce un error en el servidor, planeamos transferirlo al front-end en el siguiente formato, suponiendo que la API JSON se utilizará para esto:
{ error: 'SOME_ERROR_CODE', description: 'Something bad happened. Please try again or contact support.' }
Si en esta etapa lo que está sucediendo le parece incomprensible, no se preocupe, solo siga leyendo, intente trabajar con lo que se está discutiendo y gradualmente lo resolverá. De hecho, si hablamos de capacitación en informática, el enfoque "de arriba hacia abajo" se usa aquí, cuando las ideas generales se discuten por primera vez y luego se lleva a cabo la transición a detalles.
Así es como se ve el código del controlador de ruta.
const express = require('express') const router = express.Router() const CustomError = require('../CustomError') router.use(async (req, res) => { try { const route = require(`.${req.path}`)[req.method] try { const result = route(req)
Creemos que los comentarios en el código lo explican bastante bien. Esperamos que leerlos sea más conveniente que las explicaciones de dicho código dadas después.
Ahora eche un vistazo al archivo de ruta.
const CustomError = require('../CustomError') const GET = req => {
En estos ejemplos, no se hace nada con las consultas mismas. Simplemente considera diferentes escenarios para la ocurrencia de errores. Entonces, por ejemplo, la solicitud
GET /city
caerá en la función
const GET = req =>...
, la
POST /city
caerá en la función
const POST = req =>...
y así sucesivamente. Este esquema también funciona cuando se utilizan parámetros de consulta. Por ejemplo, para una solicitud del formulario
GET /city?startsWith=R
En general, se ha demostrado aquí que al procesar errores, la interfaz de usuario puede recibir un error general que contiene solo una oferta para volver a intentarlo o contactar al propietario del servidor, o un error generado utilizando el constructor
CustomError
que contiene información detallada sobre el problema.
Los datos de error general llegarán a la parte del cliente de la aplicación en el siguiente formulario:
{ error: 'GENERIC', description: 'Something went wrong. Please try again or contact support.' }
El constructor
CustomError
se usa así:
throw new CustomError('MY_CODE', 400, 'Error description')
Esto proporciona el siguiente código JSON pasado a la interfaz:
{ error: 'MY_CODE', description: 'Error description' }
Ahora que hemos trabajado a fondo en la parte del servidor de la aplicación, los registros de errores inútiles ya no caen en la parte del cliente. En cambio, el cliente recibe información útil sobre lo que salió mal.
No olvide que
aquí se encuentra el repositorio con el código considerado aquí. Puede descargarlo, experimentar con él y, si es necesario, adaptarlo a las necesidades de su proyecto.
3. Trabajar con errores en el cliente.
Ahora es el momento de describir la tercera parte de nuestro sistema de manejo de errores front-end. Aquí será necesario, en primer lugar, manejar los errores que ocurren en la parte cliente de la aplicación, y en segundo lugar, será necesario notificar al usuario sobre los errores que ocurren en el servidor. Primero trataremos con mostrar información de error del servidor. Como ya se mencionó, la biblioteca React se usará en este ejemplo.
▍Guardar información de error en el estado de la aplicación
Al igual que cualquier otro dato, los errores y los mensajes de error pueden cambiar, por lo que tiene sentido colocarlos en el estado de los componentes. Cuando se monta el componente, los datos de error se restablecen, por lo tanto, cuando el usuario ve por primera vez la página, no habrá mensajes de error.
El siguiente problema es que los errores del mismo tipo deben mostrarse con el mismo estilo. Por analogía con el servidor, aquí puede distinguir 3 tipos de errores.
- Errores globales: esta categoría incluye mensajes de error de naturaleza general que provienen del servidor o errores que, por ejemplo, ocurren si el usuario no ha iniciado sesión en el sistema en otras situaciones similares.
- Errores específicos generados por el lado del servidor de la aplicación: esto incluye errores que se informan desde el servidor. Por ejemplo, se produce un error similar si un usuario intentó iniciar sesión y envió un nombre de usuario y contraseña al servidor, y el servidor le informó que la contraseña era incorrecta. Tales cosas no se verifican en la parte del cliente de la aplicación, por lo que los mensajes sobre dichos errores deben provenir del servidor.
- Errores específicos generados por la parte cliente de la aplicación. Un ejemplo de dicho error es un mensaje sobre una dirección de correo electrónico no válida ingresada en el campo correspondiente.
Los errores del segundo y tercer tipo son muy similares, puede trabajar con ellos utilizando el almacén de estado de componentes del mismo nivel. Su principal diferencia es que provienen de diferentes fuentes. A continuación, analizando el código, veremos cómo trabajar con ellos.
Utilizará el sistema incorporado para administrar el estado de la aplicación en React, pero, si es necesario, puede usar soluciones especializadas para administrar el estado, como MobX o Redux.
▍ Errores globales
Típicamente, tales mensajes de error se almacenan en el componente de nivel más alto con un estado. Se muestran en un elemento de interfaz de usuario estático. Puede ser un cuadro rojo en la parte superior de la pantalla, una ventana modal o cualquier otra cosa. La implementación depende del proyecto específico. Así es como se ve el mensaje de error.
Mensaje de error globalAhora eche un vistazo al código que está almacenado en el archivo
Application.js
.
import React, { Component } from 'react' import GlobalError from './GlobalError' class Application extends Component { constructor(props) { super(props) this.state = { error: '', } this._resetError = this._resetError.bind(this) this._setError = this._setError.bind(this) } render() { return ( <div className="container"> <GlobalError error={this.state.error} resetError={this._resetError} /> <h1>Handling Errors</h1> </div> ) } _resetError() { this.setState({ error: '' }) } _setError(newError) { this.setState({ error: newError }) } } export default Application
, ,
Application.js
, . , .
GlobalError
,
x
, .
GlobalError
(
GlobalError.js
).
import React, { Component } from 'react' class GlobalError extends Component { render() { if (!this.props.error) return null return ( <div style={{ position: 'fixed', top: 0, left: '50%', transform: 'translateX(-50%)', padding: 10, backgroundColor: '#ffcccc', boxShadow: '0 3px 25px -10px rgba(0,0,0,0.5)', display: 'flex', alignItems: 'center', }} > {this.props.error} <i className="material-icons" style={{ cursor: 'pointer' }} onClick={this.props.resetError} > close </font></i> </div> ) } } export default GlobalError
if (!this.props.error) return null
. , . . , , , . , ,
x
, - , .
, ,
_setError
Application.js
. , , , , (
error: 'GENERIC'
). (
GenericErrorReq.js
).
import React, { Component } from 'react' import axios from 'axios' class GenericErrorReq extends Component { constructor(props) { super(props) this._callBackend = this._callBackend.bind(this) } render() { return ( <div> <button onClick={this._callBackend}>Click me to call the backend</button> </div> ) } _callBackend() { axios .post('/api/city') .then(result => {
, . , , . . -, , -, UX-, , — .
▍ ,
, , , .
, . . .
SpecificErrorReq.js
.
import React, { Component } from 'react' import axios from 'axios' import InlineError from './InlineError' class SpecificErrorRequest extends Component { constructor(props) { super(props) this.state = { error: '', } this._callBackend = this._callBackend.bind(this) } render() { return ( <div> <button onClick={this._callBackend}>Delete your city</button> <InlineError error={this.state.error} /> </div> ) } _callBackend() { this.setState({ error: '', }) axios .delete('/api/city') .then(result => { // - , }) .catch(err => { if (err.response.data.error === 'GENERIC') { this.props.setError(err.response.data.description) } else { this.setState({ error: err.response.data.description, }) } }) } } export default SpecificErrorRequest
, , ,
x
. , , . , , — , , , . , , . , , , — .
▍,
, , , . , , - . .
,SpecificErrorFrontend.js
, .
import React, { Component } from 'react' import axios from 'axios' import InlineError from './InlineError' class SpecificErrorRequest extends Component { constructor(props) { super(props) this.state = { error: '', city: '', } this._callBackend = this._callBackend.bind(this) this._changeCity = this._changeCity.bind(this) } render() { return ( <div> <input type="text" value={this.state.city} style={{ marginRight: 15 }} onChange={this._changeCity} /> <button onClick={this._callBackend}>Delete your city</button> <InlineError error={this.state.error} /> </div> ) } _changeCity(e) { this.setState({ error: '', city: e.target.value, }) } _validate() { if (!this.state.city.length) throw new Error('Please provide a city name.') } _callBackend() { this.setState({ error: '', }) try { this._validate() } catch (err) { return this.setState({ error: err.message }) } axios .delete('/api/city') .then(result => { // - , }) .catch(err => { if (err.response.data.error === 'GENERIC') { this.props.setError(err.response.data.description) } else { this.setState({ error: err.response.data.description, }) } }) } } export default SpecificErrorRequest
▍
, , (
GENERIC
), , . , , , , , , , , . .
Resumen
Esperamos que ahora comprenda cómo lidiar con los errores en las aplicaciones web. Algo como esto console.error(err)
debería usarse solo para propósitos de depuración, tales cosas olvidadas por el programador no deberían penetrar en la producción. Simplifica la solución al problema de iniciar sesión utilizando alguna biblioteca adecuada como loglevel .Estimados lectores! ¿Cómo manejas los errores en tus proyectos?