Catégories
Astuces et Design

Meilleure gestion des erreurs dans NodeJS avec des classes d'erreur – Smashing Magazine

A propos de l'auteur

Kelvin Omereshone est le directeur technique de 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é…
Plus à propos
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 error modèle de classe 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 n'obtient pas tout à fait l'attention qu'elle mérite vraiment. 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 manipulation ou des erreurs peuvent vous causer des problèmes assez tôt, en particulier lors du débogage d'applications.

Avant de continuer, j'aimerais souligner le type d'erreurs dont nous allons discuter de l'utilisation des classes d'erreur.

Erreurs opérationnelles

Il s'agit d'erreurs découvertes lors de l'exécution d'un programme. Les erreurs opérationnelles ne sont pas des bogues et peuvent se produire de temps en temps 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.

Vous trouverez ci-dessous d'autres exemples d'erreurs opérationnelles:

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

Il convient également d’examiner brièvement l’équivalent des erreurs opérationnelles.

Erreurs du programmeur

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 un exemple de ces erreurs:

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

 console.log(user.fullName) // throws 'undefined' because the property fullName is not defined
  • Invoquer ou appeler une fonction asynchrone sans rappel.
  • Passer une chaîne où un nombre était attendu.

Cet article est à propos de Gestion 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. Laisse-moi expliquer:

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

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
throw 'Whoops :)';

// good
throw new Error('Whoops :)')

Vous ne verrez peut-être pas le problème en lançant 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 et qui sont nécessaires 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.

Mauvais modèle de gestion des erreurs n ° 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 l'extrait de code ci-dessous:

'use strict';

const fs = require('fs');

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

write();

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 fait et nous n’en saurions 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!');
    });
}

write();

Bien que nous ayons résolu la condition de la course, nous n'avons pas encore terminé. Notre code est toujours problématique car même si nous avons utilisé un rappel 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 encore ignoré l’erreur. Nous résolvons cela en…

Gestion des erreurs avec les rappels

Afin de gérer correctement l'erreur avec les rappels, vous devez vous assurer que vous utilisez toujours l'approche d'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';


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

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

write(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 rongerait votre erreur (vos erreurs ne seront pas appelées même si cela s'est produit). 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';

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

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

write(console.log);

Mauvais modèle de gestion des erreurs n ° 2: mauvaise utilisation des promesses

Scénario du monde réel: Vous avez donc découvert Promises et vous pensez qu'elles sont bien meilleures que les rappels à cause de l'enfer des rappels et vous avez décidé de promettre une API externe sur laquelle 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 ().

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

'use strict';

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

const write = function () {
    return fs.mkdir('./writeFolder').then(() => {
        fs.writeFile('./writeFolder/foobar.txt', 'Hello world!')
    }).catch((err) => {
        // catch all potential errors
        console.error(err)
    })
}

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

'use strict';

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

const write = function () {
    return fs.mkdir('./writeFolder').then(() => {
        fs.writeFile('./writeFolder/foobar.txt', 'Hello world!').then(() => {
            // do something
        }).catch((err) => {
            console.error(err);
        })
    }).catch((err) => {
        // catch all potential errors
        console.error(err)
    })
}

Mais ce qui précède ne serait pas mis à l'échelle. En effet, si nous avons plus de chaîne de promesses à appeler, nous nous retrouverons avec quelque chose de similaire à l'enfer du 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 voudrez 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) {
    return new Promise((resolve, reject) => {
       doATask(foo, (err) => {
           if (err) {
                return reject(err);
            }

            if (arg === true) {
                resolve('I am Done')
            }
        });
    });
}

De ce qui précède, si arg n'est pas true et nous n'avons pas d'erreur de l'appel au doATask fonction alors cette promesse restera juste, ce 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) {
    return new Promise((resolve, reject) => {
        doATask(foo, (err) => {
            resolve('I’m all Done');
            throw new Error('I am never reached') // Dead Zone
        });
    });
}

De 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 tout traitement d'erreur synchrone suivant effectué dans vos promesses sera simplement avalé et ne sera jamais jeté.

Exemples du monde réel

Les exemples ci-dessus aident à expliquer les mauvais schémas de gestion des erreurs. Voyons le type de problèmes que vous pourriez rencontrer 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'était pas vraiment suffisante pour vous, vous avez donc décidé d'y ajouter votre propre message.

'use strict';

function readTemplate() {
    return new Promise(() => {
      databaseGet('query', function(err, data) {
          if (err) {
           reject('Template not found. Error: ', + err);
          } else {
            resolve(data);
          }
        });
    });
}

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 le databaseGet API 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.

Un meilleur moyen consiste à conserver l'erreur telle quelle ou à l'envelopper dans une autre erreur que vous avez créée et jointe l'erreur renvoyée à partir de l'appel databaseGet en tant que propriété.

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

Scénario: Peut-être que lorsqu'un utilisateur s'inscrit dans votre application, si une erreur se produit, vous souhaitez simplement attraper l'erreur et afficher un message personnalisé, mais vous avez complètement ignoré l'erreur qui a été détectée sans même la consigner à des fins de débogage.

router.get('/:id', function (req, res, next) {
    database.getData(req.params.userId)
    .then(function (data) {
        if (data.length) {
            res.status(200).json(data);
        } else {
            res.status(404).end();
        }
    })
    .catch(() => {
        log.error('db.rest/get: could not get data: ', req.params.userId);
        res.status(500).json({error: 'Internal server error'});
    })
});

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 peut ê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 car vous, en tant que développeur, ne sauriez 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, ce qui équivaudrait à un gaspillage de temps et d’argent de votre employeur.

Exemple réel 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.

Prenez l'exemple de code suivant ci-dessous:

async function doThings(input) {
    try {
        validate(input);
        try {
            await db.create(input);
        } catch (error) {
            error.message = `Inner error: ${error.message}`

            if (error instanceof Klass) {
                error.isKlass = true;
            }

            throw error
        }
    } catch (error) {
        error.message = `Could not do things: ${error.message}`;
        await rollback(input);
        throw error;
    }
}

Il se passe beaucoup de choses dans le code ci-dessus qui conduiraient à l'horreur du débogage. Nous allons jeter un coup d'oeil:

  • Emballage try/catch blocs: vous pouvez voir ci-dessus que nous emballons try/catch bloc qui est une très mauvaise idée. Nous essayons normalement de réduire l'utilisation de try/catch blocs 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 le but d'améliorer, 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 annulons également la base de données trop tôt car, à partir de la structure du code, il y a une forte tendance à ne pas avoir 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:

async function doThings(input) {
    validate(input);

    try {
        await db.create(input);
    } catch (error) {
        try {
            await rollback();
        } catch (error) {
            logger.log('Rollback failed', error, 'input:', input);
        }
        throw error;
    }
}

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

  • Nous en utilisons un try/catch bloc et seulement dans le bloc catch nous utilisons un autre try/catch bloc qui doit servir de garde au cas où quelque chose se passe avec cette fonction de restauration et 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.

Essai

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 des bogues qui se retrouvent en production, ce qui coûterait plus de temps de débogage.

Pointe: Assurez-vous toujours de tester non seulement les choses positives (obtenir un code d'état de 200 à partir d'un point final) mais également 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é unhandled rejections.

Voici un bref aperçu des 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 à s'exécuter.

Examinons un exemple concret courant qui conduit à des rejets non gérés.

'use strict';

async function foobar() {
    throw new Error('foobar');
}

async function baz() {
    throw new Error('baz')
}


(async function doThings() {
    const a = foobar();
    const b = baz();

    try {
        await a;
        await b;
    } catch (error) {
        // ignore all errors!
    }
})();

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 quand a est rejeté? Cela signifie await 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. Donc, le code se lirait comme suit:

'use strict';

async function foobar() {
    throw new Error('foobar');
}

async function baz() {
    throw new Error('baz')
}


(async function doThings() {
    const a = foobar();
    const b = baz();

    try {
        await Promise.all((a, b));
    } catch (error) {
        // ignore all errors!
    }
})();

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

'use strict';

async function foobar() {
    throw new Error('foobar');
}

async function doThings() {
    try {
        return foobar()
    } catch {
        // ignoring errors again !
    }
}

doThings();

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 renvoyons une promesse (foobar) avant de la traiter avec le try/catch. Ce que nous devons faire, c'est attendre la promesse que nous traitons avec le try/catch donc le code lirait:

'use strict';

async function foobar() {
    throw new Error('foobar');
}

async function doThings() {
    try {
        return await foobar()
    } catch {
        // ignoring errors again !
    }
}

doThings();

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.

Classes d'erreur

Dans ce modèle, nous commencerions notre application avec un ApplicationError class 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
    Toute erreur relative aux opérations de la base de données héritera de cette classe.
  • UserFacingError
    Toute erreur produite à la suite de l'interaction d'un utilisateur avec l'application serait héritée de cette classe.

Voici comment notre error le fichier de classe ressemblerait à:

'use strict';

// Here is the base error classes to extend from

class ApplicationError extends Error {
    get name() {
        return this.constructor.name;
    }
}

class DatabaseError extends ApplicationError { }

class UserFacingError extends ApplicationError { }

module.exports = {
    ApplicationError,
    DatabaseError,
    UserFacingError
}

Cette approche nous permet de distinguer les erreurs générées par notre application. Donc maintenant, si nous voulons gérer une mauvaise erreur de requête (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')

class BadRequestError extends UserFacingError {
    constructor(message, options = {}) {
        super(message);

        // You can attach relevant information to the error instance
        // (e.g.. the username)

        for (const (key, value) of Object.entries(options)) {
            this(key) = value;
        }
    }

    get statusCode() {
        return 400;
    }
}


class NotFoundError extends UserFacingError {
    constructor(message, options = {}) {
        super(message);

        // You can attach relevant information to the error instance
        // (e.g.. the username)

        for (const (key, value) of Object.entries(options)) {
            this(key) = value;
        }
    }
    get statusCode() {
        return 404
    }
}

module.exports = {
    BadRequestError,
    NotFoundError
}

L'un des avantages du error approche de classe est que si nous lançons 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-là (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 jetez 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 savons maintenant que toutes les informations nécessaires concernant les erreurs que nos applications peuvent générer se trouvent au même 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', async function (req, res, next) {
    let data

    try {
        data = await database.getData(req.params.userId)
    } catch (err) {
        return next(err);
    }

    if (!data.length) {
        return next(new NotFoundError('Dataset not found'));
    }

    res.status(200).json(data)
})

app.use(function (err, req, res, next) {
    if (err instanceof UserFacingError) {
        res.sendStatus(err.statusCode);

        // or

        res.status(err.statusCode).send(err.errorCode)
    } else {
        res.sendStatus(500)
    }

    // do your logic
    logger.error(err, 'Parameters: ', req.params, 'User data: ', req.user)
});

À partir 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() dans les endroits où nous traitons des erreurs. Cet appel transmettrait les erreurs au gestionnaire qui est défini dans le app.use section. Parce qu'express ne prend pas en charge async / await, nous utilisons try/catch blocs.

Donc, à partir du code ci-dessus, pour gérer nos erreurs, nous devons simplement vérifier si l'erreur qui a été générée est un UserFacingError instance 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 également avoir un code d'erreur spécifique que vous pouvez transmettre au client) et c'est à peu près tout.

Vous remarquerez également que dans ce modèle (error modèle de classe) toute autre erreur que vous n'avez pas explicitement lancée est une 500 erreur car il s'agit de quelque chose d'inattendu qui signifie que vous n'avez pas explicitement renvoyé cette erreur dans votre application. De cette façon, nous sommes en mesure de 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 des classes d'erreur spécialement configurées pour votre application;
  • Implémenter des gestionnaires d'erreurs abstraits;
  • Utilisez toujours async / await;
  • Rendre les erreurs expressives;
  • L'utilisateur promet si nécessaire;
  • Renvoie les statuts et codes d'erreur appropriés;
  • Utilisez des crochets de promesse.
Éditorial fracassant(ra, yk, il)

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *