Principes de gestion des erreurs pour les applications Node.js + Express.js?

177

Il semble que le signalement / la gestion des erreurs se fasse différemment dans les applications Node.js + Express.js par rapport aux autres frameworks. Ai-je raison de comprendre que cela fonctionne comme suit?

A) Détectez les erreurs en les recevant comme paramètres de vos fonctions de rappel. Par exemple:

doSomethingAndRunCallback(function(err) { 
    if(err) {  }
});

B) Signaler les erreurs dans MIDDLEWARE en appelant next (err). Exemple:

handleRequest(req, res, next) {
    // An error occurs…
    next(err);
}

C) Signalez les erreurs dans ROUTES en lançant l'erreur. Exemple:

app.get('/home', function(req, res) {
    // An error occurs
    throw err;
});

D) Gérez les erreurs en configurant votre propre gestionnaire d'erreurs via app.error () ou utilisez le gestionnaire d'erreurs Connect générique. Exemple:

app.error(function(err, req, res, next) {
    console.error(err);
    res.send('Fail Whale, yo.');
});

Ces quatre principes sont-ils à la base de toutes les opérations de traitement / signalement des erreurs dans les applications Node.js + Express.js?

Clint Harris
la source

Réponses:

183

La gestion des erreurs dans Node.js est généralement au format A). La plupart des rappels renvoient un objet d'erreur comme premier argument ounull .

Express.js utilise le middleware et la syntaxe du middleware utilise B) et E) (mentionnés ci-dessous).

C) est une mauvaise pratique si vous me demandez.

app.get('/home', function(req, res) {
    // An error occurs
    throw err;
});

Vous pouvez facilement réécrire ce qui précède comme

app.get('/home', function(req, res, next) {
    // An error occurs
    next(err);
});

La syntaxe du middleware est valide dans un get requête.

Quant à D)

(07:26:37 PM) tjholowaychuk: app.error est supprimé dans 3.x

TJ vient de confirmer que app.error est obsolète en faveur de E

E)

app.use(function(err, req, res, next) {
  // Only handle `next(err)` calls
});

Tout middleware d'une longueur de 4 (4 arguments) est considéré comme un middleware d'erreur. Quand on appelle next(err)connect va et appelle un middleware basé sur des erreurs.

Raynos
la source
11
Merci! Pour tous ceux qui pourraient rencontrer cela à l'avenir, il semble que l'ordre des paramètres de la "méthode e" soit en fait err, req, res, next (au lieu de req, res, next, err).
Clint Harris
9
Cela a l'air génial, mais un problème que je vois est que certaines erreurs ne parviennent jamais aux gestionnaires d'erreurs que vous décrivez, et ne peuvent être interceptées que par un gestionnaire process.on ('uncaughtException', fn). La sagesse conventionnelle est de laisser cela se produire et de compter sur Forever ou autre pour redémarrer l'application, mais si vous faites cela, comment renvoyer une page d'erreur conviviale?
Paul
1
@chovy Aussi, juste un fyi. Le gestionnaire d'erreurs doit être donné à l'application après l'erreur lancée / suivante. Si c'est avant, il ne détectera pas l'erreur.
Lee Olayvar
3
next (err) est essentiellement la version d'Express de lancer une erreur, vous devez cependant l'appeler explicitement dans votre propre middleware
qodeninja
1
@qodeninja Cette méthode est considérée comme une meilleure pratique dans Express.
David Oliveros
11

Les gens de Joyent ont publié un document de bonnes pratiques vraiment perspicace à ce sujet. Un article incontournable pour tout développeur Node.js.

hthserhs
la source
Excellent article, correction du lien pour pointer vers le document mis à jour de Joyent.
stephbu
2
article pas mal: mais trop de texte et pas assez d'exemples c'est un article pour de vrais professionnels
Gerd
3

Pourquoi le premier paramètre?

En raison de la nature asynchrone de Node.js, le premier paramètre-as-err modèle est devenu bien établi comme une convention pour la gestion des erreurs userland Node.js . C'est parce que asynchrone:

try {
    setTimeout(function() {
        throw 'something broke' //Some random error
    }, 5)
}
catch(e) {
   //Will never get caught
}

Donc, à la place, avoir le premier argument du rappel est à peu près le seul moyen raisonnable de transmettre des erreurs de manière asynchrone autre que de simplement les lancer.

Faire ainsi se traduira par un unhandled exceptionqui, juste de la façon dont il sonne, implique que rien n'a été fait pour sortir l'application de son état confus.

Exceptions, pourquoi existent-elles

Il convient de noter cependant que pratiquement toutes les parties de Node.js sont des émetteurs d'événements et que le lancement d'une exception est un événement de bas niveau qui peut être géré comme tous les événements:

//This won't immediately crash if connection fails
var socket = require("net").createConnection(5000);
socket.on("error", function(err) {
    console.error("calm down...", err)
});

Cela ne doit pas être poussé à l'extrême pour détecter toutes les erreurs et créer une application qui fera de son pour ne jamais planter. C'est une idée terrible dans presque tous les cas d'utilisation, car cela laissera le développeur sans aucune idée de ce qui se passe dans l'état de l'application et revient à envelopper main dans try-catch.

Domaines - regrouper les événements de manière logique

Dans le cadre de la gestion de ce problème d'exceptions faisant tomber les applications, les domaines permettent au développeur de prendre, par exemple, l'application Express.js, et d'essayer de fermer les connexions de manière raisonnable en cas de défaillance catastrophique.

ES6

Il est probablement mentionné que cela changera à nouveau car ES6 permet au modèle de générateur de créer des événements asynchrones qui sont toujours capturables avec des blocs try / catch.

Koa (écrit par TJ Holowaychuck, même auteur original d'Express.js) fait cela de manière notable. Il utilise l' yieldinstruction ES6 pour créer des blocs qui, tout en paraissant presque synchrones, sont gérés de la manière habituelle des nœuds asynchrones:

app.use(function *(next) {
    try {
        yield next;
    } 
    catch (err) {
        this.status = err.status || 500;
        this.body = err.message;
        this.app.emit('error', err, this);
    }
});

app.use(function *(next) {
    throw new Error('some error');
})

Cet exemple a été volé sans vergogne d' ici .

David
la source