Manejo de errores en Express

Cuando comencé a trabajar con Express y traté de descubrir cómo manejar los errores, tuve dificultades. Había una sensación de que nadie escribió sobre lo que necesitaba. Como resultado, tuve que buscar respuestas a mis preguntas yo mismo. Hoy quiero contar todo lo que sé sobre el manejo de errores en aplicaciones Express. Comencemos con errores sincrónicos.



Manejo sincrónico de errores


Si necesita manejar un error síncrono, puede, en primer lugar, usar la instrucción throw , throw dicho error en el controlador de solicitud Express. Tenga en cuenta que los controladores de solicitudes también se denominan "controladores", pero prefiero utilizar el término "controlador de solicitudes", ya que me parece más claro.

Así es como se ve:

 app.post('/testing', (req, res) => {  throw new Error('Something broke! ') }) 

Dichos errores pueden detectarse utilizando el controlador de errores Express. Si no ha escrito su propio controlador de errores (hablaremos más sobre esto a continuación), Express manejará el error utilizando el controlador predeterminado.

Esto es lo que hace el controlador de errores Express estándar:

  1. Establece el código de estado de respuesta HTTP en 500.
  2. Envía una respuesta de texto a la entidad que ejecuta la solicitud.
  3. Registra una respuesta de texto en la consola.


Mensaje de error mostrado en la consola

Manejo asincrónico de errores


Para manejar errores asincrónicos, debe enviar un error al controlador de errores Express a través del next argumento:

 app.post('/testing', async (req, res, next) => {  return next(new Error('Something broke again! ')) }) 

Esto es lo que llega a la consola al registrar este error.


Mensaje de error mostrado en la consola

Si usa la construcción async / await en una aplicación Express, necesitará usar una función de contenedor como express-async-handler . Esto le permite escribir código asincrónico sin bloques try / catch . Lea más sobre async / await en Express aquí .

 const asyncHandler = require('express-async-handler') app.post('/testing', asyncHandler(async (req, res, next) => {  //  - })) 

Después de que el controlador de solicitud se envuelva en express-async-handler , puede, como se describió anteriormente, lanzar un error utilizando la instrucción throw . Este error irá al controlador de errores Express.

 app.post('/testing', asyncHandler(async (req, res, next) => {  throw new Error('Something broke yet again! ') })) 


Mensaje de error mostrado en la consola

Escribir su propio controlador de errores


Los manejadores de errores expresos toman 4 argumentos:

  1. error
  2. req
  3. res
  4. siguiente

Debe colocarlos después de controladores intermedios y rutas.

 app.use(/*...*/) app.get(/*...*/) app.post(/*...*/) app.put(/*...*/) app.delete(/*...*/) //           app.use((error, req, res, next) => { /* ... */ }) 

Si crea su propio controlador de errores, Express dejará de usar el controlador de errores estándar. Para manejar el error, debe generar una respuesta para la aplicación front-end que abordó el punto final en el que ocurrió el error. Esto significa que debe hacer lo siguiente:

  1. Genere y envíe un código de estado de respuesta adecuado.
  2. Formule y envíe una respuesta adecuada.

El código de estado apropiado en cada caso particular depende de lo que sucedió exactamente. Aquí hay una lista de errores comunes que debe estar preparado para manejar:

  1. Error 400 Bad Request . Usado en dos situaciones. En primer lugar, cuando el usuario no incluyó el campo requerido en la solicitud (por ejemplo, el campo con la información de la tarjeta de crédito no se completó en el formulario de pago enviado). En segundo lugar, cuando la solicitud contiene datos incorrectos (por ejemplo, ingresar diferentes contraseñas en el campo de contraseña y en el campo de confirmación de contraseña).
  2. Error 401 Unauthorized . Este código de estado de respuesta se aplica si el usuario ha ingresado credenciales incorrectas (como nombre de usuario, dirección de correo electrónico o contraseña).
  3. Error 403 Forbidden . Se utiliza cuando el usuario no tiene acceso al punto final.
  4. Error 404 Not Found . Se utiliza en casos donde el punto final no se puede detectar.
  5. Error 500 Internal Server Error Error 500 Internal Server Error . Se aplica cuando la solicitud enviada por el front-end se forma correctamente, pero se produjo algún error en el back-end.

Después de definir el código de estado de respuesta apropiado, debe establecerse usando res.status :

 app.use((error, req, res, next) => {  // ,         res.status(400)  res.json(/* ... */) }) 

El código de estado de respuesta debe corresponder al mensaje de error. Para hacer esto, envíe el código de estado junto con el error.

La forma más fácil de hacer esto es con el paquete http-errors . Permite enviar tres datos por error:

  1. El código de estado de respuesta.
  2. El mensaje asociado con el error.
  3. Cualquier dato que deba enviarse (esto es opcional).

Aquí se explica cómo instalar el paquete de http-errors :

 npm install http-errors --save 

Aquí se explica cómo usar este paquete:

 const createError = require('http-errors') //   throw createError(status, message, properties) 

Considere un ejemplo que le permitirá comprender esto correctamente.

Imagine que estamos tratando de encontrar un usuario en su dirección de correo electrónico. Pero este usuario no se puede encontrar. Como resultado, decidimos enviar un error de User not found en respuesta a la solicitud correspondiente, informando a la persona que llama que no se encontró al usuario.

Esto es lo que necesitaremos hacer al crear el error:

  1. Establezca el código de estado de respuesta en 400 Bad Request (después de todo, el usuario ingresó datos incorrectos). Este será nuestro primer parámetro.
  2. Envíe un mensaje a la persona que llama como User not found . Este será el segundo parámetro.

 app.put('/testing', asyncHandler(async (req, res) => {  const { email } = req.body  const user = await User.findOne({ email })  //     -    if (!user) throw createError(400, `User '${email}' not found`) })) 

Puede obtener el código de estado usando la construcción error.status y el mensaje de error usando error.message :

 //   app.use((error, req, res, next) => {  console.log('Error status: ', error.status)  console.log('Message: ', error.message) }) 


El resultado de errores de registro en la consola

Luego, el estado de respuesta se establece usando res.status , y el mensaje se escribe en res.json :

 app.use((error, req, res, next) => {  //      res.status(error.status)  //    res.json({ message: error.message }) }) 

Personalmente, prefiero enviar un código de estado, un mensaje y el resultado del seguimiento de la pila en tales respuestas. Esto facilita la depuración.

 app.use((error, req, res, next) => {  //      res.status(error.status)  //    res.json({    status: error.status,    message: error.message,    stack: error.stack  }) }) 

▍ Código de estado de respuesta predeterminado


Si la fuente del error no es createError , entonces no tendrá la propiedad de status . Aquí hay un ejemplo en el que se intentó leer un archivo inexistente usando fs.readFile :

 const fs = require('fs') const util = require('util') //  readFile  ,  ,  async/await-. //     : https://zellwk.com/blog/callbacks-to-promises const readFilePromise = util.promisify(fs.readFile) app.get('/testing', asyncHandler(async (req, res, next) => {  const data = await readFilePromise('some-file') }) 

Tal objeto de error no tendrá la propiedad de status :

 app.use((error, req, res, next) => {  console.log('Error status: ', error.status)  console.log('Message: ', error.message) }) 


El resultado de errores de registro en la consola

En tales casos, puede establecer el código de error predeterminado. Es decir, estamos hablando del 500 Internal Server Error :

 app.use((error, req, res, next) => {  res.status(error.status || 500)  res.json({    status: error.status,    message: error.message,    stack: error.stack  }) }) 

▍ Cambiar el código de estado de error


Supongamos que vamos a leer un determinado archivo utilizando los datos proporcionados por el usuario. Si dicho archivo no existe, significa que debemos dar un error 400 Bad Request . Después de todo, no se puede encontrar el servidor porque no se puede encontrar el archivo.

En este caso, debe usar la construcción try / catch para detectar el error original. Luego debe volver a crear el objeto de error usando createError :

 app.get('/testing', asyncHandler(async (req, res, next) => {  try {    const { file } = req.body    const contents = await readFilePromise(path.join(__dirname, file))  } catch (error) {    throw createError(400, `File ${file} does not exist`)  } }) 

▍ 404 manejo de errores


Si la solicitud pasó por todos los controladores y rutas intermedios, pero no se procesó, esto significa que no se encontró el punto final correspondiente a dicha solicitud.

Para manejar los errores 404 Not Found , debe agregar, entre las rutas y el controlador de errores, un controlador adicional. Así es como se ve la creación del objeto de error 404:

 //  ... // ... app.use((req, res, next) => {  next(createError(404)) }) //  ... 


Detalles del error

▍ ERR_HTTP_HEADERS_SENT notas de error


No entre en pánico si ve el mensaje de error ERR_HTTP_HEADERS_SENT: Cannot set headers after they are sent to the client . Surge porque en el mismo controlador se llama repetidamente al método que establece los encabezados de respuesta. Estos son los métodos que llaman para configurar automáticamente los encabezados de respuesta:

  1. enviar res.
  2. res.json
  3. res.render
  4. res.sendFile
  5. res.sendStatus
  6. res.end
  7. res.redirect

Entonces, por ejemplo, si llama a los res.json res.render y res.json en el mismo manejador de respuestas, obtendrá el error ERR_HTTP_HEADERS_SENT :

 app.get('/testing', (req, res) => {  res.render('new-page')  res.json({ message: '¯\_(ツ)_/¯' }) }) 

Como resultado, si encuentra este error, verifique cuidadosamente el código del controlador de respuestas y asegúrese de que no haya situaciones en las que se invoquen varios de los métodos descritos anteriormente.

▍ Manejo de errores y transmisión de datos


Si algo sale mal al transmitir la respuesta a la interfaz, entonces puede encontrar el mismo error ERR_HTTP_HEADERS_SENT .

En este caso, el manejo de errores debe pasarse a los manejadores estándar. Dicho controlador enviará un error y cerrará automáticamente la conexión.

 app.use((error, req, res, next) => {  //       ,        if (res.headersSent) {    return next(error)  }  //     }) 

Resumen


Hoy les conté todo lo que sé sobre el manejo de errores en Express. Espero que esto te ayude a escribir aplicaciones Express más confiables.

Estimados lectores! ¿Cómo maneja los errores en sus proyectos Node.js?


Source: https://habr.com/ru/post/476290/


All Articles