Fermer

septembre 1, 2020

Meilleure gestion des erreurs dans NodeJS avec des classes d'erreur


À propos de l'auteur

Kelvin Omereshone est le directeur technique du Quru Lab . Kelvin était auparavant ingénieur Front-end chez myPadi.ng. Il est le créateur de la communauté Nuxtjs Africa et très passionné…
En savoir plus sur
Kelvin

Cet article est destiné aux développeurs JavaScript et NodeJS qui souhaitent améliorer la gestion des erreurs dans leurs applications. Kelvin Omereshone explique le modèle de classe error et comment l'utiliser pour une manière plus efficace et plus efficace de gérer les erreurs dans vos applications.

La gestion des erreurs est l'une de ces parties du développement logiciel qui ne sont pas tout à fait obtenir toute l’attention qu’elle mérite. Cependant, la création d'applications robustes nécessite de traiter correctement les erreurs.

Vous pouvez vous débrouiller dans NodeJS sans gérer correctement les erreurs, mais en raison de la nature asynchrone de NodeJS, une mauvaise gestion ou des erreurs peuvent vous causer des problèmes assez tôt, en particulier lors du débogage des applications. [19659006] Avant de continuer, j'aimerais souligner le type d'erreurs dont nous allons discuter comment utiliser les classes d'erreurs.

Erreurs opérationnelles

Ce sont des erreurs découvertes pendant l'exécution d'un programme. Les erreurs opérationnelles ne sont pas des bogues et peuvent survenir de temps à autre principalement en raison d'un ou d'une combinaison de plusieurs facteurs externes comme l'expiration du délai d'un serveur de base de données ou la décision d'un utilisateur de tenter une injection SQL en entrant des requêtes SQL dans un champ de saisie. [19659006] Voici d'autres exemples d'erreurs opérationnelles:

  • Échec de la connexion à un serveur de base de données;
  • Entrées non valides par l'utilisateur (le serveur répond avec un code de réponse 400 );
  • Délai d'expiration de la demande ;
  • Ressource introuvable (le serveur répond avec un code de réponse 404);
  • Le serveur retourne avec une réponse 500 .

Il convient également de noter brièvement la contrepartie des erreurs opérationnelles.

Erreurs de programmation

Ce sont des bogues dans le programme qui peuvent être résolus en changeant le code. Ces types d'erreurs ne peuvent pas être traités car ils se produisent à la suite de la rupture du code. Voici des exemples d'erreurs:

  • Tentative de lecture d'une propriété sur un objet qui n'est pas défini.
  const user = {
   prénom: 'Kelvin',
   nom de famille: 'Omereshone',
 }

 console.log (user.fullName) // renvoie "undefined" car la propriété fullName n'est pas définie 
  • Appel ou appel d'une fonction asynchrone sans rappel.
  • Transmission d'une chaîne à l'endroit où un nombre était attendu.

Ceci L'article porte sur Traitement des erreurs opérationnelles dans NodeJS. La gestion des erreurs dans NodeJS est très différente de la gestion des erreurs dans d'autres langues. Cela est dû à la nature asynchrone de JavaScript et à l'ouverture de JavaScript aux erreurs. Laissez-moi vous expliquer:

En JavaScript, les instances de la classe error ne sont pas la seule chose que vous pouvez lancer. Vous pouvez littéralement lancer n'importe quel type de données, cette ouverture n'est pas autorisée par d'autres langages.

Par exemple, un développeur JavaScript peut décider d'ajouter un nombre au lieu d'une instance d'objet d'erreur, comme ceci:

 // bad
lancer «Oups :)»;

// bien
lancer une nouvelle erreur ('Whoops :)')

Vous ne verrez peut-être pas le problème en jetant d'autres types de données, mais cela entraînera un débogage plus difficile car vous n'obtiendrez pas de trace de pile et d'autres propriétés que l'objet Error expose qui sont nécessaire pour le débogage.

Examinons quelques modèles incorrects dans la gestion des erreurs, avant de jeter un coup d'œil au modèle de classe Error et en quoi c'est un bien meilleur moyen de gérer les erreurs dans NodeJS.

Bad Error Handling Pattern # 1: Mauvaise utilisation des rappels

Scénario du monde réel : Votre code dépend d'une API externe nécessitant un rappel pour obtenir le résultat que vous attendez de lui.

Prenons le ci-dessous l'extrait de code:

 'use strict';

const fs = require ('fs');

const write = function () {
    fs.mkdir ('./ writeFolder');
    fs.writeFile ('./ writeFolder / foobar.txt', 'Hello World');
}

écrire();

Jusqu'à NodeJS 8 et au-dessus, le code ci-dessus était légitime, et les développeurs se contentaient de lancer et d'oublier les commandes. Cela signifie que les développeurs n'étaient pas obligés de fournir un rappel à ces appels de fonction et pouvaient donc omettre la gestion des erreurs. Que se passe-t-il lorsque le writeFolder n’a pas été créé? L'appel à writeFile ne sera pas effectué et nous n'en saurons rien. Cela peut également entraîner une condition de concurrence, car la première commande peut ne pas s'être terminée lorsque la deuxième commande a redémarré, vous ne le sauriez pas.

Commençons par résoudre ce problème en résolvant la condition de concurrence. Nous le ferions en donnant un rappel à la première commande mkdir pour s'assurer que le répertoire existe bien avant d'y écrire avec la deuxième commande. Donc notre code ressemblerait à celui ci-dessous:

 'use strict';

const fs = require ('fs');

const write = function () {
    fs.mkdir ('./ writeFolder', () => {
        fs.writeFile ('./ writeFolder / foobar.txt', 'Hello World!');
    });
}

écrire();

Bien que nous ayons résolu la condition de race, nous n'avons pas encore tout à fait terminé. Notre code reste problématique car même si nous avons utilisé un callback pour la première commande, nous n'avons aucun moyen de savoir si le dossier writeFolder a été créé ou non. Si le dossier n’a pas été créé, le deuxième appel échouera à nouveau, mais nous avons ignoré l’erreur une fois de plus. Nous résolvons cela en…

Gestion des erreurs avec les rappels

Afin de gérer correctement les erreurs avec les rappels, vous devez vous assurer de toujours utiliser l'approche «erreur d'abord». Cela signifie que vous devez d'abord vérifier s'il y a une erreur renvoyée par la fonction avant de continuer à utiliser les données (le cas échéant) qui ont été renvoyées. Voyons la mauvaise façon de procéder:

 'use strict';


// Faux
const fs = require ('fs');

const write = function (rappel) {
    fs.mkdir ('./ writeFolder', (err, data) => {
        if (data) fs.writeFile ('./ writeFolder / foobar.txt', 'Hello World!');
        autre rappel (err)
    });
}

écrire (console.log);

Le modèle ci-dessus est incorrect car l'API que vous appelez peut parfois ne pas renvoyer de valeur ou renvoyer une valeur erronée comme valeur de retour valide. Cela vous ferait vous retrouver dans un cas d'erreur même si vous pourriez apparemment avoir un appel réussi de la fonction ou de l'API.

Le modèle ci-dessus est également mauvais car son utilisation mangerait votre erreur (vos erreurs ne seront pas appelées même si cela aurait pu arriver). Vous n'aurez également aucune idée de ce qui se passe dans votre code suite à ce type de modèle de gestion des erreurs. Donc, la bonne façon pour le code ci-dessus serait:

 'use strict';

// Droite
const fs = require ('fs');

const write = function (rappel) {
    fs.mkdir ('./ writeFolder', (err, data) => {
        if (err) return callback (err)
        fs.writeFile ('./ writeFolder / foobar.txt', 'Hello World!');
    });
}

écrire (console.log);

Mauvais schéma de gestion des erreurs n ° 2: mauvaise utilisation des promesses

Scénario réel : Vous avez donc découvert les promesses et vous pensez qu'elles sont bien meilleures que les rappels à cause de l'enfer des rappels et vous a décidé de promettre une API externe dont dépendait votre base de code. Ou vous consommez une promesse d'une API externe ou d'une API de navigateur comme la fonction fetch ().

De nos jours, nous n'utilisons pas vraiment de rappels dans nos bases de code NodeJS, nous utilisons promesses . Réimplémentons donc notre exemple de code avec une promesse:

 'use strict';

const fs = require ('fs'). promesses;

const write = function () {
    retourne fs.mkdir ('./ writeFolder'). then (() => {
        fs.writeFile ('./ writeFolder / foobar.txt', 'Bonjour tout le monde!')
    }). catch ((err) => {
        // attraper toutes les erreurs potentielles
        console.error (err)
    })
}

Mettons le code ci-dessus sous un microscope – nous pouvons voir que nous dérivons la promesse fs.mkdir dans une autre chaîne de promesse (l'appel à fs.writeFile) sans même traiter cet appel de promesse. Vous pourriez penser qu'une meilleure façon de le faire serait:

 'use strict';

const fs = require ('fs'). promesses;

const write = function () {
    retourne fs.mkdir ('./ writeFolder'). then (() => {
        fs.writeFile ('./ writeFolder / foobar.txt', 'Hello world!'). then (() => {
            // faire quelque chose
        }). catch ((err) => {
            console.error (err);
        })
    }). catch ((err) => {
        // attraper toutes les erreurs potentielles
        console.error (err)
    })
}

Mais ce qui précède ne serait pas mis à l'échelle. C'est parce que si nous avons plus de chaîne de promesses à appeler, nous nous retrouverons avec quelque chose de similaire à l'enfer de rappel que les promesses ont été faites pour résoudre. Cela signifie que notre code continuera d'être indenté vers la droite. Nous aurions une promesse d'enfer entre nos mains.

Promettre une API basée sur le rappel

La plupart du temps, vous voudriez promettre une API basée sur le rappel par vous-même afin de mieux gérer les erreurs sur cette API. Cependant, ce n'est pas vraiment facile à faire. Prenons un exemple ci-dessous pour expliquer pourquoi.

 function doesWillNotAlwaysSettle (arg) {
    retourner une nouvelle promesse ((résoudre, rejeter) => {
       doATask (foo, (err) => {
           if (err) {
                retourner rejeter (err);
            }

            si (arg === vrai) {
                résoudre ('I am Done')
            }
        });
    });
}

D'après ce qui précède, si arg n'est pas true et que nous n'avons pas d'erreur de l'appel à la fonction doATask alors cette promesse sera juste hang out qui est une fuite de mémoire dans votre application.

Erreurs de synchronisation avalées dans les promesses

L'utilisation du constructeur Promise présente plusieurs difficultés, l'une de ces difficultés est; dès qu'il est résolu ou rejeté, il ne peut pas obtenir un autre état. En effet, une promesse ne peut obtenir qu'un seul état – soit elle est en attente, soit elle est résolue / rejetée. Cela signifie que nous pouvons avoir des zones mortes dans nos promesses. Voyons ceci dans le code:

 function deadZonePromise (arg) {
    retourner une nouvelle promesse ((résoudre, rejeter) => {
        doATask (foo, (err) => {
            résoudre («je suis tout fait»);
            throw new Error ('Je ne suis jamais atteint') // Dead Zone
        });
    });
}

D'après ce qui précède, nous voyons que dès que la promesse est résolue, la ligne suivante est une zone morte et ne sera jamais atteinte. Cela signifie que toute gestion d'erreur synchrone suivante exécutée dans vos promesses sera simplement avalée et ne sera jamais lancée.

Exemples du monde réel

Les exemples ci-dessus aident à expliquer les mauvais schémas de gestion des erreurs, jetons un coup d'œil au type de problèmes vous pourriez voir dans la vie réelle.

Exemple du monde réel # 1 – Transformer une erreur en chaîne

Scénario : Vous avez décidé que l'erreur renvoyée par une API n'est pas vraiment suffisante pour vous vous avez donc décidé d'y ajouter votre propre message.

 'use strict';

function readTemplate () {
    retourne une nouvelle promesse (() => {
      databaseGet ('requête', fonction (err, données) {
          if (err) {
           rejeter ("Modèle introuvable. Erreur:", + err);
          } autre {
            résoudre (données);
          }
        });
    });
}

readTemplate ();

Voyons ce qui ne va pas avec le code ci-dessus. D'après ce qui précède, nous voyons que le développeur tente d'améliorer l'erreur générée par l'API databaseGet en concaténant l'erreur renvoyée avec la chaîne «Template not found». Cette approche présente de nombreux inconvénients car lorsque la concaténation a été effectuée, le développeur exécute implicitement toString sur l'objet d'erreur renvoyé. De cette façon, il perd toutes les informations supplémentaires renvoyées par l'erreur (dites adieu à la trace de pile). Donc, ce que le développeur a en ce moment est juste une chaîne qui n'est pas utile lors du débogage.

Une meilleure façon est de garder l'erreur telle quelle ou de l'envelopper dans une autre erreur que vous avez créée et jointe l'erreur renvoyée par databaseGet call en tant que propriété.

Exemple réel n ° 2: Ignorer complètement l'erreur

Scénario : Peut-être lorsqu'un utilisateur s'inscrit dans votre application, en cas d'erreur vous voulez juste attraper l'erreur et afficher un message personnalisé mais vous avez complètement ignoré l'erreur qui a été interceptée sans même la consigner à des fins de débogage.

 router.get ('/: id', function (req, res, suivant) {
    database.getData (req.params.userId)
    .then (fonction (données) {
        if (data.length) {
            res.status (200) .json (données);
        } autre {
            res.status (404) .end ();
        }
    })
    .catch (() => {
        log.error ('db.rest/get: n'a pas pu obtenir les données:', req.params.userId);
        res.status (500) .json ({error: 'Erreur interne du serveur'});
    })
});

De ce qui précède, nous pouvons voir que l'erreur est complètement ignorée et que le code envoie 500 à l'utilisateur si l'appel à la base de données a échoué. Mais en réalité, la cause de l'échec de la base de données pourrait être des données malformées envoyées par l'utilisateur, ce qui est une erreur avec le code d'état 400.

Dans le cas ci-dessus, nous nous retrouverions dans une horreur de débogage parce que vous en tant que le développeur ne saurait pas ce qui ne va pas. L'utilisateur ne sera pas en mesure de fournir un rapport correct, car 500 erreurs de serveur interne sont toujours générées. Vous finiriez par perdre des heures à trouver le problème qui équivaudrait à un gaspillage de temps et d'argent de votre employeur.

Exemple concret n ° 3: ne pas accepter l'erreur générée par une API

Scénario : Une erreur a été générée par une API que vous utilisiez, mais vous n'acceptez pas cette erreur à la place, vous la gérez et la transformez de manière à la rendre inutile à des fins de débogage.

Prenons l'exemple de code suivant ci-dessous:

 fonction asynchrone doThings (entrée) {
    essayez {
        valider (entrée);
        essayez {
            attendre db.create (entrée);
        } catch (erreur) {
            error.message = `Erreur interne: $ {error.message}`

            if (instance d'erreur de Klass) {
                error.isKlass = true;
            }

            erreur de jet
        }
    } catch (erreur) {
        error.message = `Impossible de faire les choses: $ {error.message}`;
        attendre le retour en arrière (entrée);
        lancer une erreur;
    }
}

Il se passe beaucoup de choses dans le code ci-dessus qui mèneraient à l'horreur du débogage. Jetons un coup d’œil:

  • Wrapping try / catch blocks: Vous pouvez voir ci-dessus que nous enveloppons try / catch block, ce qui est une très mauvaise idée. Nous essayons normalement de réduire l'utilisation des blocs try / catch pour minimiser la surface où nous aurions à gérer notre erreur (pensez-y comme traitement d'erreur DRY);
  • Nous manipulons également le message d'erreur dans la tentative d'amélioration, ce qui n'est pas non plus une bonne idée;
  • Nous vérifions si l'erreur est une instance de type Klass et dans ce cas, nous définissons une propriété booléenne de l'erreur ] isKlass à truev (mais si cette vérification réussit, l'erreur est du type Klass );
  • Nous restaurons également la base de données trop tôt car, à partir de la structure du code, il y a une tendance élevée que nous n'aurions peut-être même pas atteint la base de données lorsque l'erreur a été générée.

Voici une meilleure façon d'écrire le code ci-dessus:

 fonction async doThings (entrée) {
    valider (entrée);

    essayez {
        attendre db.create (entrée);
    } catch (erreur) {
        essayez {
            attendre rollback ();
        } catch (erreur) {
            logger.log ('Rollback failed', erreur, 'input:', input);
        }
        lancer une erreur;
    }
}

Analysons ce que nous faisons correctement dans l'extrait de code ci-dessus:

  • Nous utilisons un bloc try / catch et seulement dans le bloc catch nous utilisons un autre try / catch ] qui doit servir de garde au cas où quelque chose se passe avec cette fonction d'annulation et que nous enregistrons cela;
  • Enfin, nous lançons notre erreur reçue d'origine, ce qui signifie que nous ne perdons pas le message inclus dans cette erreur. [19659084] Test

    Nous voulons surtout tester notre code (manuellement ou automatiquement). Mais la plupart du temps, nous ne testons que les choses positives. Pour un test robuste, vous devez également tester les erreurs et les cas extrêmes. Cette négligence est responsable de la découverte de bogues dans la production, ce qui coûterait plus de temps de débogage supplémentaire.

    Astuce : Assurez-vous toujours de tester non seulement les choses positives (obtenir un code d'état de 200 à partir d'un point final) mais aussi tous les cas d'erreur et tous les cas extrêmes.

    Exemple réel n ° 4: Rejections non gérées

    Si vous avez déjà utilisé des promesses, vous avez probablement rencontré non géré rejets .

    Voici une brève introduction sur les rejets non gérés. Les refus non traités sont des refus de promesse qui n'ont pas été traités. Cela signifie que la promesse a été rejetée mais que votre code continuera à fonctionner.

    Examinons un exemple courant du monde réel qui conduit à des rejets non gérés.

     'use strict';
    
    fonction asynchrone foobar () {
        lancer une nouvelle erreur ('foobar');
    }
    
    fonction asynchrone baz () {
        lancer une nouvelle erreur ('baz')
    }
    
    
    (fonction asynchrone doThings () {
        const a = foobar ();
        const b = baz ();
    
        essayez {
            attendre un;
            attendre b;
        } catch (erreur) {
            // ignore toutes les erreurs!
        }
    }) ();
    

    Le code ci-dessus à première vue peut ne pas sembler sujet aux erreurs. Mais en y regardant de plus près, nous commençons à voir un défaut. Laissez-moi vous expliquer: que se passe-t-il lorsque un est rejeté? Cela signifie que attendre b n'est jamais atteint et cela signifie que c'est un rejet non géré. Une solution possible est d'utiliser Promise.all sur les deux promesses. Ainsi, le code se lirait comme suit:

     'use strict';
    
    fonction asynchrone foobar () {
        lancer une nouvelle erreur ('foobar');
    }
    
    fonction asynchrone baz () {
        lancer une nouvelle erreur ('baz')
    }
    
    
    (fonction asynchrone doThings () {
        const a = foobar ();
        const b = baz ();
    
        essayez {
            attendre Promise.all ([a, b]);
        } catch (erreur) {
            // ignore toutes les erreurs!
        }
    }) ();
    

    Voici un autre scénario réel qui conduirait à une erreur de rejet de promesse non gérée:

     'use strict';
    
    fonction asynchrone foobar () {
        lancer une nouvelle erreur ('foobar');
    }
    
    fonction asynchrone doThings () {
        essayez {
            retour foobar ()
        } capture {
            // ignorer à nouveau les erreurs!
        }
    }
    
    faire des choses();
    

    Si vous exécutez l'extrait de code ci-dessus, vous obtiendrez un rejet de promesse non géré, et voici pourquoi: Bien que ce ne soit pas évident, nous retournons une promesse (foobar) avant de la traiter avec le try / catch . Ce que nous devons faire, c'est attendre la promesse que nous gérons avec le try / catch pour que le code se lise:

     'use strict';
    
    fonction asynchrone foobar () {
        lancer une nouvelle erreur ('foobar');
    }
    
    fonction asynchrone doThings () {
        essayez {
            retour attendre foobar ()
        } capture {
            // ignorer à nouveau les erreurs!
        }
    }
    
    faire des choses();
    

    Conclusion sur les choses négatives

    Maintenant que vous avez vu de mauvais modèles de gestion des erreurs et des correctifs possibles, examinons maintenant le modèle de classe Error et comment il résout le problème de la mauvaise gestion des erreurs dans NodeJS.

    Error Classes

    Dans ce modèle, nous commencerions notre application avec une classe ApplicationError de cette façon nous savons que toutes les erreurs dans nos applications que nous lançons explicitement vont en hériter. Nous commencerions donc par les classes d'erreur suivantes:

    • ApplicationError
      Il s'agit de l'ancêtre de toutes les autres classes d'erreur, c'est-à-dire que toutes les autres classes d'erreur en héritent.
    • DatabaseError
      Any erreur relative aux opérations de la base de données héritera de cette classe.
    • UserFacingError
      Toute erreur produite suite à l'interaction d'un utilisateur avec l'application serait héritée de cette classe.

    Voici comment notre ] erreur le fichier de classe ressemblerait à:

     'use strict';
    
    // Voici les classes d'erreur de base à étendre
    
    La classe ApplicationError étend l'erreur {
        obtenir le nom () {
            retourne this.constructor.name;
        }
    }
    
    La classe DatabaseError étend ApplicationError {}
    
    La classe UserFacingError étend ApplicationError {}
    
    module.exports = {
        Erreur d'application,
        Erreur de la base de données,
        UserFacingError
    }
    

    Cette approche nous permet de distinguer les erreurs générées par notre application. Donc maintenant, si nous voulons gérer une erreur de requête incorrecte (entrée utilisateur invalide) ou une erreur non trouvée (ressource non trouvée), nous pouvons hériter de la classe de base qui est UserFacingError (comme dans le code ci-dessous).

     const {UserFacingError} = require ('./ baseErrors')
    
    La classe BadRequestError étend UserFacingError {
        constructeur (message, options = {}) {
            super (message);
    
            // Vous pouvez joindre des informations pertinentes à l'instance d'erreur
            // (par exemple, le nom d'utilisateur)
    
            for (const [key, value] of Object.entries (options)) {
                this [key] = valeur;
            }
        }
    
        get statusCode () {
            return 400;
        }
    }
    
    
    La classe NotFoundError étend UserFacingError {
        constructeur (message, options = {}) {
            super (message);
    
            // Vous pouvez joindre des informations pertinentes à l'instance d'erreur
            // (par exemple, le nom d'utilisateur)
    
            for (const [key, value] of Object.entries (options)) {
                this [key] = valeur;
            }
        }
        get statusCode () {
            retour 404
        }
    }
    
    module.exports = {
        BadRequestError,
        Erreur non trouvée
    }
    

    L'un des avantages de l'approche de classe error est que si nous lançons l'une de ces erreurs, par exemple, une NotFoundError chaque développeur lisant cette base de code serait en mesure de comprendre ce qui se passe à ce moment (s’ils lisent le code).

    Vous pourrez également transmettre plusieurs propriétés spécifiques à chaque classe d’erreur lors de l’instanciation de cette erreur.

    Un autre avantage clé est que vous pouvez avoir des propriétés qui font toujours partie d'une classe d'erreur, par exemple, si vous recevez une erreur UserFacing, vous savez qu'un statusCode fait toujours partie de cette classe d'erreur maintenant vous pouvez simplement l'utiliser directement dans le code plus tard.

    Conseils sur l'utilisation des classes d'erreur

    • Créez votre propre module (éventuellement privé) pour chaque classe d'erreur de cette façon, vous pouvez simplement l'importer dans votre application et l'utiliser partout.
    • Ne renvoyez que les erreurs qui vous intéressent (erreurs qui sont des instances de vos classes d'erreur). De cette façon, vous savez que vos classes d'erreur sont votre seule source de vérité et qu'elles contiennent toutes les informations nécessaires pour déboguer votre application.
    • Avoir un module d'erreur abstrait est très utile car nous connaissons maintenant toutes les informations nécessaires concernant les erreurs que nos applications peuvent générer. un endroit.
    • Gérez les erreurs dans les couches. Si vous gérez des erreurs partout, vous avez une approche incohérente de la gestion des erreurs qui est difficile à suivre. Par couches, j'entends comme base de données, couches express / fastify / HTTP, etc.

    Voyons à quoi ressemblent les classes d'erreur dans le code. Voici un exemple en express:

     const {DatabaseError} = require ('./ error')
    const {NotFoundError} = require ('./ userFacingErrors')
    const {UserFacingError} = require ('./ error')
    
    // Express
    app.get ('/: id', fonction asynchrone (req, res, next) {
        laisser les données
    
        essayez {
            data = attendre database.getData (req.params.userId)
        } catch (err) {
            retourne suivant (err);
        }
    
        if (! data.length) {
            return next (new NotFoundError ('Dataset not found'));
        }
    
        res.status (200) .json (données)
    })
    
    app.use (function (err, req, res, next) {
        if (err instanceof UserFacingError) {
            res.sendStatus (err.statusCode);
    
            // ou
    
            res.status (err.statusCode) .send (err.errorCode)
        } autre {
            res.sendStatus (500)
        }
    
        // fais ta logique
        logger.error (err, 'Paramètres:', req.params, 'Données utilisateur:', req.user)
    });
    

    De ce qui précède, nous tirons parti du fait qu'Express expose un gestionnaire d'erreurs global qui vous permet de gérer toutes vos erreurs en un seul endroit. Vous pouvez voir l'appel à next () aux endroits où nous traitons les erreurs. Cet appel transmettrait les erreurs au gestionnaire qui est défini dans la section app.use . Parce qu'express ne prend pas en charge async / await, nous utilisons les blocs try / catch .

    Donc, à partir du code ci-dessus, pour gérer nos erreurs, nous devons simplement vérifier si l'erreur qui a été renvoyée est une ] UserFacingError et automatiquement nous savons qu'il y aurait un statusCode dans l'objet d'erreur et nous l'envoyons à l'utilisateur (vous voudrez peut-être aussi avoir un code d'erreur spécifique que vous pouvez transmettre au client) et c'est

    Vous remarquerez également que dans ce modèle ( error class pattern) toute autre erreur que vous n'avez pas explicitement renvoyée est une erreur 500 car il s'agit de quelque chose d'inattendu cela signifie que vous n'avez pas explicitement renvoyé cette erreur dans votre application. De cette façon, nous pouvons distinguer les types d'erreurs qui se produisent dans nos applications.

    Conclusion

    Une gestion correcte des erreurs dans votre application peut vous permettre de mieux dormir la nuit et gagner du temps de débogage. Voici quelques points clés à retenir de cet article:

    • Utilisez les classes d'erreur spécifiquement configurées pour votre application;
    • Implémentez des gestionnaires d'erreurs abstraits;
    • Utilisez toujours async / await;
    • Rendez les erreurs expressives; [19659012] L'utilisateur promet si nécessaire;
    • Renvoie les statuts et codes d'erreur appropriés;
    • Utilisez les hooks de promesse.
     Smashing Editorial (ra, yk, il)




Source link