Gestion des erreurs dans Express

Lorsque j'ai commencé à travailler avec Express et essayé de comprendre comment gérer les erreurs, j'ai eu du mal. Il y avait le sentiment que personne n'avait écrit sur ce dont j'avais besoin. En conséquence, j'ai dû chercher moi-même des réponses à mes questions. Aujourd'hui, je veux dire tout ce que je sais sur la gestion des erreurs dans les applications Express. Commençons par les erreurs synchrones.



Gestion des erreurs synchrones


Si vous devez gérer une erreur synchrone, vous pouvez tout d'abord, à l'aide de l'instruction throw , throw une telle erreur dans le gestionnaire de requêtes Express. Veuillez noter que les gestionnaires de demandes sont également appelés «contrôleurs», mais je préfère utiliser le terme «gestionnaire de demandes» comme il me semble plus clairement.

Voici à quoi ça ressemble:

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

Ces erreurs peuvent être détectées à l'aide du gestionnaire d'erreurs Express. Si vous n'avez pas écrit votre propre gestionnaire d'erreurs (nous en parlerons plus loin ci-dessous), Express traitera l'erreur en utilisant le gestionnaire par défaut.

Voici ce que fait le gestionnaire d'erreurs Express standard:

  1. Définit le code d'état de réponse HTTP sur 500.
  2. Envoie une réponse textuelle à l'entité exécutant la demande.
  3. Enregistre une réponse textuelle dans la console.


Message d'erreur affiché dans la console

Gestion des erreurs asynchrones


Pour gérer les erreurs asynchrones, vous devez envoyer une erreur au gestionnaire d'erreurs Express via l'argument next :

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

C'est ce qui arrive à la console lors de la journalisation de cette erreur.


Message d'erreur affiché dans la console

Si vous utilisez la construction async / wait dans une application Express, vous devrez utiliser une fonction wrapper comme express-async-handler . Cela vous permet d'écrire du code asynchrone sans blocs try / catch . En savoir plus sur async / wait dans Express ici .

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

Une fois que le gestionnaire de requêtes est encapsulé dans express-async-handler , vous pouvez, comme décrit ci-dessus, générer une erreur à l'aide de l'instruction throw . Cette erreur ira au gestionnaire d'erreurs Express.

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


Message d'erreur affiché dans la console

Écrire votre propre gestionnaire d'erreurs


Les gestionnaires d'erreur express prennent 4 arguments:

  1. erreur
  2. req
  3. res
  4. suivant

Vous devez les placer après les gestionnaires et les itinéraires intermédiaires.

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

Si vous créez votre propre gestionnaire d'erreurs, Express cessera d'utiliser le gestionnaire d'erreurs standard. Pour gérer l'erreur, vous devez générer une réponse pour l'application frontale qui a adressé le point de terminaison auquel l'erreur s'est produite. Cela signifie que vous devez effectuer les opérations suivantes:

  1. Générez et envoyez un code d'état de réponse approprié.
  2. Formez et envoyez une réponse appropriée.

Le code d'état approprié dans chaque cas particulier dépend de ce qui s'est exactement passé. Voici une liste des erreurs courantes que vous devez être prêt à gérer:

  1. Erreur 400 Bad Request . Utilisé dans deux situations. Premièrement, lorsque l'utilisateur n'a pas inclus le champ obligatoire dans la demande (par exemple, le champ avec les informations de carte de crédit n'a pas été rempli dans le formulaire de paiement envoyé). Deuxièmement, lorsque la demande contient des données incorrectes (par exemple, entrer différents mots de passe dans le champ de mot de passe et dans le champ de confirmation de mot de passe).
  2. Erreur 401 Unauthorized . Ce code d'état de réponse est appliqué si l'utilisateur a entré des informations d'identification incorrectes (telles que nom d'utilisateur, adresse e-mail ou mot de passe).
  3. Erreur 403 Forbidden . Utilisé lorsque l'utilisateur n'est pas autorisé à accéder au point de terminaison.
  4. Erreur 404 Not Found . Il est utilisé dans les cas où le point final ne peut pas être détecté.
  5. Erreur 500 Internal Server Error Erreur de 500 Internal Server Error . Il est appliqué lorsque la demande envoyée par le serveur frontal est correctement formée, mais une erreur s'est produite sur le serveur principal.

Une fois le code d'état de réponse approprié défini, il doit être défini à l'aide de res.status :

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

Le code d'état de réponse doit correspondre au message d'erreur. Pour ce faire, envoyez le code d'état avec l'erreur.

La façon la plus simple de le faire est d'utiliser le package http-errors . Il permet d'envoyer trois informations par erreur:

  1. Le code d'état de réponse.
  2. Le message associé à l'erreur.
  3. Toutes les données qui doivent être envoyées (ceci est facultatif).

Voici comment installer le package http-errors :

 npm install http-errors --save 

Voici comment utiliser ce package:

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

Prenons un exemple qui vous permettra de bien comprendre tout cela.

Imaginez que nous essayons de trouver un utilisateur à son adresse e-mail. Mais cet utilisateur est introuvable. En conséquence, nous décidons d'envoyer une erreur User not found en réponse à la demande correspondante, informant l'appelant que l'utilisateur n'a pas été trouvé.

Voici ce que nous devrons faire lors de la création de l'erreur:

  1. Définissez le code d'état de réponse sur 400 Bad Request (après tout, l'utilisateur a entré des données incorrectes). Ce sera notre premier paramètre.
  2. Envoyez un message à l'appelant comme User not found . Ce sera le deuxième paramètre.

 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`) })) 

Vous pouvez obtenir le code d'état à l'aide de la construction error.status et le message d'erreur à l'aide de error.message :

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


Le résultat des erreurs de journalisation dans la console

Ensuite, l'état de réponse est défini à l'aide de res.status et le message est écrit dans res.json :

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

Personnellement, je préfère envoyer un code d'état, un message et un résultat de trace de pile dans de telles réponses. Cela facilite le débogage.

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

▍ Code d'état de réponse par défaut


Si la source de l'erreur n'est pas createError , elle n'aura pas la propriété status . Voici un exemple dans lequel une tentative de lecture d'un fichier inexistant a été effectuée à l'aide de 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') }) 

Un tel objet d'erreur n'aura pas la propriété status :

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


Le résultat des erreurs de journalisation dans la console

Dans de tels cas, vous pouvez définir le code d'erreur par défaut. À savoir, nous parlons de l' 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  }) }) 

▍Modification du code d'état d'erreur


Supposons que nous allons lire un certain fichier en utilisant les données fournies par l'utilisateur. Si un tel fichier n'existe pas, cela signifie que nous devons donner une erreur 400 Bad Request . Après tout, le serveur est introuvable car le fichier est introuvable.

Dans ce cas, vous devez utiliser la construction try / catch pour intercepter l'erreur d'origine. Ensuite, vous devez recréer l'objet d'erreur à l'aide de 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`)  } }) 

▍ Gestion des erreurs 404


Si la demande a traversé tous les gestionnaires et itinéraires intermédiaires, mais n'a pas été traitée, cela signifie que le point de terminaison correspondant à une telle demande n'a pas été trouvé.

Pour gérer les erreurs 404 Not Found , vous devez ajouter, entre les routes et le gestionnaire d'erreurs, un gestionnaire supplémentaire. Voici à quoi ressemble la création de l'objet d'erreur 404:

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


Détails de l'erreur

▍ ERR_HTTP_HEADERS_SENT notes d'erreur


Ne paniquez pas si vous voyez le message d'erreur ERR_HTTP_HEADERS_SENT: Cannot set headers after they are sent to the client . Cela se produit parce que dans le même gestionnaire, la méthode qui définit les en-têtes de réponse est appelée à plusieurs reprises. Voici les méthodes qui appellent pour définir automatiquement les en-têtes de réponse:

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

Ainsi, par exemple, si vous appelez les res.json res.render et res.json dans le même gestionnaire de réponses, vous obtiendrez l'erreur ERR_HTTP_HEADERS_SENT :

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

Par conséquent, si vous rencontrez cette erreur, vérifiez soigneusement le code du gestionnaire de réponses et assurez-vous qu'il n'y a aucune situation dans laquelle plusieurs des méthodes décrites ci-dessus sont appelées.

▍ Gestion des erreurs et streaming de données


Si quelque chose ne va pas lors du streaming de la réponse vers le frontend, vous pouvez rencontrer la même erreur ERR_HTTP_HEADERS_SENT .

Dans ce cas, la gestion des erreurs doit être transmise aux gestionnaires standard. Un tel gestionnaire enverra une erreur et fermera automatiquement la connexion.

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

Résumé


Aujourd'hui, je vous ai dit tout ce que je sais sur la gestion des erreurs dans Express. J'espère que cela vous aidera à écrire des applications Express plus fiables.

Chers lecteurs! Comment gérez-vous les erreurs dans vos projets Node.js?


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


All Articles