Question Node.js Gestion des exceptions aux meilleures pratiques


J'ai juste commencé à essayer node.js il y a quelques jours. J'ai réalisé que le nœud est terminé chaque fois que j'ai une exception non gérée dans mon programme. Ceci est différent du conteneur de serveur normal auquel j'ai été exposé, où seul le thread de travail meurt lorsque des exceptions non gérées se produisent et que le conteneur peut toujours recevoir la requête. Cela soulève quelques questions:

  • Est process.on('uncaughtException') le seul moyen efficace de se prémunir contre cela?
  • Volonté process.on('uncaughtException') attraper l'exception non gérée lors de l'exécution de processus asynchrones?
  • Existe-t-il un module déjà construit (comme l'envoi d'e-mails ou l'écriture dans un fichier) que je pourrais utiliser dans le cas d'exceptions non interceptées?

J'apprécierais n'importe quel pointeur / article qui me montrerait les meilleures pratiques courantes pour manipuler des exceptions non saisies dans node.js


645
2017-09-05 16:15


origine


Réponses:


Mise à jour: Joyent a maintenant leur propre guide mentionné dans cette réponse. Les informations suivantes sont plus d'un résumé:

"Jeter" des erreurs en toute sécurité

Idéalement, nous aimerions éviter autant que possible les erreurs non interceptées, au lieu de lancer littéralement l'erreur, nous pouvons à la place "lancer" l'erreur en utilisant l'une des méthodes suivantes en fonction de notre architecture de code:

  • Pour le code synchrone, si une erreur survient, renvoyez l'erreur:

    // Define divider as a syncrhonous function
    var divideSync = function(x,y) {
        // if error condition?
        if ( y === 0 ) {
            // "throw" the error safely by returning it
            return new Error("Can't divide by zero")
        }
        else {
            // no error occured, continue on
            return x/y
        }
    }
    
    // Divide 4/2
    var result = divideSync(4,2)
    // did an error occur?
    if ( result instanceof Error ) {
        // handle the error safely
        console.log('4/2=err', result)
    }
    else {
        // no error occured, continue on
        console.log('4/2='+result)
    }
    
    // Divide 4/0
    result = divideSync(4,0)
    // did an error occur?
    if ( result instanceof Error ) {
        // handle the error safely
        console.log('4/0=err', result)
    }
    else {
        // no error occured, continue on
        console.log('4/0='+result)
    }
    
  • Pour le code basé sur le rappel (c'est-à-dire asynchrone), le premier argument du rappel est err, si une erreur se produit err est l'erreur, si une erreur ne se produit pas alors err est null. Tout autre argument suit le err argument:

    var divide = function(x,y,next) {
        // if error condition?
        if ( y === 0 ) {
            // "throw" the error safely by calling the completion callback
            // with the first argument being the error
            next(new Error("Can't divide by zero"))
        }
        else {
            // no error occured, continue on
            next(null, x/y)
        }
    }
    
    divide(4,2,function(err,result){
        // did an error occur?
        if ( err ) {
            // handle the error safely
            console.log('4/2=err', err)
        }
        else {
            // no error occured, continue on
            console.log('4/2='+result)
        }
    })
    
    divide(4,0,function(err,result){
        // did an error occur?
        if ( err ) {
            // handle the error safely
            console.log('4/0=err', err)
        }
        else {
            // no error occured, continue on
            console.log('4/0='+result)
        }
    })
    
  • Pour mouvementé code, où l'erreur peut arriver n'importe où, au lieu de jeter l'erreur, le feu error événement à la place:

    // Definite our Divider Event Emitter
    var events = require('events')
    var Divider = function(){
        events.EventEmitter.call(this)
    }
    require('util').inherits(Divider, events.EventEmitter)
    
    // Add the divide function
    Divider.prototype.divide = function(x,y){
        // if error condition?
        if ( y === 0 ) {
            // "throw" the error safely by emitting it
            var err = new Error("Can't divide by zero")
            this.emit('error', err)
        }
        else {
            // no error occured, continue on
            this.emit('divided', x, y, x/y)
        }
    
        // Chain
        return this;
    }
    
    // Create our divider and listen for errors
    var divider = new Divider()
    divider.on('error', function(err){
        // handle the error safely
        console.log(err)
    })
    divider.on('divided', function(x,y,result){
        console.log(x+'/'+y+'='+result)
    })
    
    // Divide
    divider.divide(4,2).divide(4,0)
    

En toute sécurité "attraper" des erreurs

Parfois cependant, il peut toujours y avoir un code qui renvoie une erreur quelque part qui peut conduire à une exception non interceptée et à un crash potentiel de notre application si nous ne l'attrapons pas en toute sécurité. Selon notre architecture de code, nous pouvons utiliser l'une des méthodes suivantes pour l'attraper:

  • Quand nous savons où l'erreur se produit, nous pouvons envelopper cette section dans un domaine node.js

    var d = require('domain').create()
    d.on('error', function(err){
        // handle the error safely
        console.log(err)
    })
    
    // catch the uncaught errors in this asynchronous or synchronous code block
    d.run(function(){
        // the asynchronous or synchronous code that we want to catch thrown errors on
        var err = new Error('example')
        throw err
    })
    
  • Si nous savons où l'erreur se produit est un code synchrone, et pour une raison quelconque ne peut pas utiliser les domaines (peut-être l'ancienne version du noeud), nous pouvons utiliser l'instruction try catch:

    // catch the uncaught errors in this synchronous code block
    // try catch statements only work on synchronous code
    try {
        // the synchronous code that we want to catch thrown errors on
        var err = new Error('example')
        throw err
    } catch (err) {
        // handle the error safely
        console.log(err)
    }
    

    Cependant, veillez à ne pas utiliser try...catch dans le code asynchrone, car une erreur lancée de manière asynchrone ne sera pas interceptée:

    try {
        setTimeout(function(){
            var err = new Error('example')
            throw err
        }, 1000)
    }
    catch (err) {
        // Example error won't be caught here... crashing our app
        // hence the need for domains
    }
    

    Une autre chose à faire attention avec try...catch est le risque d'enrouler votre rappel de fin dans le try déclaration comme ça:

    var divide = function(x,y,next) {
        // if error condition?
        if ( y === 0 ) {
            // "throw" the error safely by calling the completion callback
            // with the first argument being the error
            next(new Error("Can't divide by zero"))
        }
        else {
            // no error occured, continue on
            next(null, x/y)
        }
    }
    
    var continueElsewhere = function(err, result){
            throw new Error('elsewhere has failed')
    }
    
    try {
            divide(4, 2, continueElsewhere)
            // ^ the execution of divide, and the execution of 
            //   continueElsewhere will be inside the try statement
    }
    catch (err) {
            console.log(err.stack)
            // ^ will output the "unexpected" result of: elsewhere has failed
    }
    

    Ce gotcha est très facile à faire car votre code devient plus complexe. En tant que tel, il est préférable d'utiliser des domaines ou de renvoyer des erreurs pour éviter (1) les exceptions non interceptées dans le code asynchrone (2) l'exécution catch catch catch que vous ne le souhaitez pas. Dans les langues qui permettent un threading approprié au lieu du style de machine à événements asynchrone de JavaScript, cela pose moins de problème.

  • Enfin, dans le cas où une erreur non interceptée se produit dans un endroit qui n'a pas été enveloppé dans un domaine ou une instruction try catch, nous pouvons faire en sorte que notre application ne plante pas en utilisant le uncaughtException auditeur (cependant, cela peut mettre l'application dans un état inconnu):

    // catch the uncaught errors that weren't wrapped in a domain or try catch statement
    // do not use this in modules, but only in applications, as otherwise we could have multiple of these bound
    process.on('uncaughtException', function(err) {
        // handle the error safely
        console.log(err)
    })
    
    // the asynchronous or synchronous code that emits the otherwise uncaught error
    var err = new Error('example')
    throw err
    

639
2018-04-18 20:10



Vous trouverez ci-dessous une synthèse et une curation de nombreuses sources sur ce sujet, y compris un exemple de code et des citations de posts de blogs sélectionnés. La liste complète des meilleures pratiques peut être trouvé ici


Meilleures pratiques de gestion des erreurs Node.JS


Number1: Utiliser des promesses pour la gestion des erreurs asynchrones

TL; DR: Gérer les erreurs asynchrones dans le style de rappel est probablement le moyen le plus rapide d'atteindre l'enfer (a.k.a la pyramide du malheur). Le meilleur cadeau que vous pouvez donner à votre code est d'utiliser à la place une bibliothèque de promesses de bonne réputation qui fournit une syntaxe de code beaucoup plus compacte et familière comme try-catch

Autrement: Le style de rappel Node.JS, fonction (err, response), est un moyen prometteur pour le code non maintenable en raison du mélange de la gestion des erreurs avec le code occasionnel, de l'imbrication excessive et des modèles de codage maladroits

Exemple de code - bon

doWork()
.then(doWork)
.then(doError)
.then(doWork)
.catch(errorHandler)
.then(verify);

exemple de code anti pattern - gestion des erreurs de style callback

getData(someParameter, function(err, result){
    if(err != null)
      //do something like calling the given callback function and pass the error
    getMoreData(a, function(err, result){
          if(err != null)
            //do something like calling the given callback function and pass the error
        getMoreData(b, function(c){ 
                getMoreData(d, function(e){ 
                    ...
                });
            });
        });
    });
});

Blog citation: "Nous avons un problème avec les promesses" (Sur le blog pouchdb, classé 11ème pour les mots clés "Node Promises")

"... Et en fait, les rappels font quelque chose de plus sinistre: ils nous privent de la pile, ce que nous prenons généralement pour acquis dans les langages de programmation." Ecrire du code sans pile, c'est comme conduire une voiture sans pédale de frein: ne réalisez pas à quel point vous en avez besoin, jusqu'à ce que vous l'atteigniez et que ce ne soit pas là. Le but des promesses est de nous redonner les fondamentaux de la langue que nous avons perdus quand nous sommes allés async: le retour, le lancer et la pile. Mais vous devez savoir comment utiliser correctement les promesses pour en tirer profit."


Number2: utilisez uniquement l'objet Error intégré

TL; DR: Il est assez courant de voir un code qui renvoie des erreurs sous forme de chaîne ou de type personnalisé - ce qui complique la logique de traitement des erreurs et l'interopérabilité entre les modules. Que vous rejetiez une promesse, rejetiez une exception ou que vous émettiez une erreur - l'utilisation de l'objet d'erreur intégré Node.JS augmente l'uniformité et empêche la perte d'informations d'erreur

Autrement: Lors de l'exécution de certains modules, le fait d'être incertain du type d'erreur qui en résulte rend la tâche beaucoup plus difficile à raisonner sur l'exception à venir et à la gérer. Même vaut la peine, en utilisant des types personnalisés pour décrire les erreurs pourraient conduire à la perte d'informations d'erreur critiques comme la trace de la pile!

Exemple de code - le faire correctement

    //throwing an Error from typical function, whether sync or async
 if(!productToAdd)
 throw new Error("How can I add new product when no value provided?");

//'throwing' an Error from EventEmitter
const myEmitter = new MyEmitter();
myEmitter.emit('error', new Error('whoops!'));

//'throwing' an Error from a Promise
 return new promise(function (resolve, reject) {
 DAL.getProduct(productToAdd.id).then((existingProduct) =>{
 if(existingProduct != null)
 return reject(new Error("Why fooling us and trying to add an existing product?"));

exemple de code anti pattern

//throwing a String lacks any stack trace information and other important properties
if(!productToAdd)
    throw ("How can I add new product when no value provided?");

Blog citation: "Une chaîne n'est pas une erreur" (Du blog devthought, classé 6 pour les mots-clés "objet d'erreur Node.JS")

"... le passage d'une chaîne à la place d'une erreur entraîne une interopérabilité réduite entre les modules.Il rompt les contrats avec les API qui effectuent des vérifications d'instance de type instanceof, ou qui veulent en savoir plus sur l'erreur. Les objets d'erreur, comme nous le verrons, ont des propriétés très intéressantes dans les moteurs JavaScript modernes en plus de contenir le message passé au constructeur. "


Number3: Distinguer les erreurs opérationnelles et celles du programmeur

TL; DR: Les erreurs de fonctionnement (par exemple API reçue une entrée non valide) se rapportent à des cas connus où l'impact de l'erreur est entièrement compris et peut être géré de manière réfléchie. D'autre part, une erreur de programmeur (par exemple essayer de lire une variable indéfinie) fait référence à des échecs de code inconnus qui dictent de redémarrer avec élégance l'application.

Autrement: Vous pouvez toujours redémarrer l'application lorsqu'une erreur apparaît, mais pourquoi laisser ~ 5000 utilisateurs en ligne à cause d'une erreur mineure et prédite (erreur de fonctionnement)? le contraire n'est pas idéal non plus - garder l'application en place lorsqu'un problème inconnu (erreur de programmeur) se produit peut entraîner un comportement imprévu. Différencier les deux permet d'agir avec tact et d'appliquer une approche équilibrée basée sur le contexte donné

Exemple de code - le faire correctement

    //throwing an Error from typical function, whether sync or async
 if(!productToAdd)
 throw new Error("How can I add new product when no value provided?");

//'throwing' an Error from EventEmitter
const myEmitter = new MyEmitter();
myEmitter.emit('error', new Error('whoops!'));

//'throwing' an Error from a Promise
 return new promise(function (resolve, reject) {
 DAL.getProduct(productToAdd.id).then((existingProduct) =>{
 if(existingProduct != null)
 return reject(new Error("Why fooling us and trying to add an existing product?"));

exemple de code - marquage d'une erreur comme opérationnelle (fiable)

//marking an error object as operational 
var myError = new Error("How can I add new product when no value provided?");
myError.isOperational = true;

//or if you're using some centralized error factory (see other examples at the bullet "Use only the built-in Error object")
function appError(commonType, description, isOperational) {
    Error.call(this);
    Error.captureStackTrace(this);
    this.commonType = commonType;
    this.description = description;
    this.isOperational = isOperational;
};

throw new appError(errorManagement.commonErrors.InvalidInput, "Describe here what happened", true);

//error handling code within middleware
process.on('uncaughtException', function(error) {
    if(!error.isOperational)
        process.exit(1);
});

Blog Quote: "Sinon, vous risquez l'état" (Du blog débogable, classé 3 pour les mots clés "Node.JS uncaught exception")

"... De par la nature même de jeter fonctionne en JavaScript, il n'y a presque jamais moyen de «reprendre là où vous l'avez laissé» en toute sécurité, sans fuites de références, ou en créant une autre sorte d'état fragile indéfini. Le moyen le plus sûr de répondre à une erreur lancée est d'arrêter le processus. Bien sûr, dans un serveur Web normal, vous pouvez avoir de nombreuses connexions ouvertes, et il n'est pas raisonnable de les fermer brusquement car une erreur a été déclenchée par quelqu'un d'autre. La meilleure approche consiste à envoyer une réponse d'erreur à la demande qui a déclenché l'erreur, tout en laissant les autres terminer dans leur temps normal, et cesser d'écouter de nouvelles demandes dans ce travailleur "


Number4: gérer les erreurs de manière centralisée, mais pas au sein du middleware

TL; DR: Les erreurs de gestion de la logique telles que le courrier à l'administrateur et la journalisation doivent être encapsulées dans un objet dédié et centralisé que tous les points d'extrémité (par exemple Express middleware, cron jobs, unit-testing) appellent lorsqu'une erreur survient.

Autrement: Ne pas traiter les erreurs dans un seul endroit conduira à la duplication de code et probablement à des erreurs mal gérées

Exemple de code - un flux d'erreur typique

//DAL layer, we don't handle errors here
DB.addDocument(newCustomer, (error, result) => {
    if (error)
        throw new Error("Great error explanation comes here", other useful parameters)
});

//API route code, we catch both sync and async errors and forward to the middleware
try {
    customerService.addNew(req.body).then(function (result) {
        res.status(200).json(result);
    }).catch((error) => {
        next(error)
    });
}
catch (error) {
    next(error);
}

//Error handling middleware, we delegate the handling to the centrzlied error handler
app.use(function (err, req, res, next) {
    errorHandler.handleError(err).then((isOperationalError) => {
        if (!isOperationalError)
            next(err);
    });
});

Citation de blog: "Parfois, les niveaux inférieurs ne peuvent rien faire d'utile, sauf propager l'erreur à leur appelant" (Du blog Joyent, classé 1 pour les mots-clés "Node.JS error handling")

"... Vous pouvez finir par gérer la même erreur à plusieurs niveaux de la pile, ce qui arrive lorsque les niveaux inférieurs ne peuvent rien faire d'utile, sauf propager l'erreur à leur appelant, qui propage l'erreur à son appelant, et ainsi de suite. Seul l'appelant de haut niveau sait quelle est la réponse appropriée, que ce soit pour réessayer l'opération, signaler une erreur à l'utilisateur ou autre chose, mais cela ne signifie pas que vous devriez essayer de signaler toutes les erreurs à un seul niveau supérieur. callback, car ce callback lui-même ne peut pas savoir dans quel contexte l'erreur s'est produite "


Number5: erreurs d'API de document à l'aide de Swagger

TL; DR: Laissez vos appelants API savoir quelles erreurs peuvent revenir en retour afin qu'ils puissent gérer ces sans réfléchir. Cela est généralement fait avec des frameworks de documentation d'API REST comme Swagger

Autrement:Un client API peut décider de planter et de redémarrer uniquement parce qu'il a reçu une erreur qu'il ne pouvait pas comprendre. Note: l'appelant de votre API pourrait être vous (très typique dans un environnement de microservices)

Citation de blog: "Vous devez dire à vos appelants quelles erreurs peuvent se produire" (Sur le blog Joyent, classé 1 pour les mots-clés "Node.JS logging")

... Nous avons parlé de la façon de gérer les erreurs, mais lorsque vous écrivez une nouvelle fonction, comment livrez-vous des erreurs au code qui a appelé votre fonction? ... Si vous ne savez pas quelles erreurs peuvent se produire ou si vous ne savez pas ce qu'elles signifient, alors votre programme ne peut être correct que par accident. Donc, si vous écrivez une nouvelle fonction, vous devez dire à vos appelants quelles erreurs peuvent se produire et ce qu'ils


Number6: Ferme le processus gracieusement quand un étranger vient en ville

TL; DR: Lorsqu'une erreur inconnue se produit (erreur de développeur, voir la meilleure pratique numéro 3), il existe une incertitude quant à la salubrité de l'application. Une pratique courante suggère de redémarrer le processus avec soin en utilisant un outil de «restarter» comme Forever et PM2.

Autrement: Lorsqu'une exception inconnue est détectée, un objet peut être dans un état défectueux (par exemple, un émetteur d'événements utilisé globalement et ne déclenche plus d'événements en raison d'une défaillance interne) et toutes les demandes futures peuvent échouer ou se comporter de manière folle

Exemple de code - décider s'il faut planter

//deciding whether to crash when an uncaught exception arrives
//Assuming developers mark known operational errors with error.isOperational=true, read best practice #3
process.on('uncaughtException', function(error) {
 errorManagement.handler.handleError(error);
 if(!errorManagement.handler.isTrustedError(error))
 process.exit(1)
});


//centralized error handler encapsulates error-handling related logic 
function errorHandler(){
 this.handleError = function (error) {
 return logger.logError(err).then(sendMailToAdminIfCritical).then(saveInOpsQueueIfCritical).then(determineIfOperationalError);
 }

 this.isTrustedError = function(error)
 {
 return error.isOperational;
 }

Citation de blog: "Il y a trois écoles de pensées sur le traitement des erreurs" (Du blog jsrecipes)

... Il y a principalement trois écoles de pensées sur la gestion des erreurs: 1. Laisser l'application tomber en panne et le redémarrer. 2. Gérer toutes les erreurs possibles et ne jamais tomber en panne. 3. Approche équilibrée entre les deux


Number7: Utiliser un enregistreur mature pour augmenter la visibilité des erreurs

TL; DR: Un ensemble d'outils de journalisation mature tels que Winston, Bunyan ou Log4J accélèrera la découverte et la compréhension des erreurs. Donc oubliez console.log.

Autrement: Parcourir les fichiers console.logs ou manuellement à l'aide d'un fichier texte désordonné sans avoir besoin d'interroger les outils ou un visualiseur de journal décent pourrait vous occuper au travail jusqu'à la fin

Exemple de code - Logger Winston en action

//your centralized logger object
var logger = new winston.Logger({
 level: 'info',
 transports: [
 new (winston.transports.Console)(),
 new (winston.transports.File)({ filename: 'somefile.log' })
 ]
 });

//custom code somewhere using the logger
logger.log('info', 'Test Log Message with some parameter %s', 'some parameter', { anything: 'This is metadata' });

Citation de blog: "Permet d'identifier quelques exigences (pour un enregistreur):" (Sur le blog strongblog)

... Permet d'identifier quelques exigences (pour un enregistreur):   1. Horodate chaque ligne de journal. Celui-ci est assez explicite - vous devriez être capable de dire quand chaque entrée de journal s'est produite.   2. Le format de journalisation devrait être facilement digestible par les humains et les machines.   3. Permet plusieurs flux de destination configurables. Par exemple, vous pouvez écrire des journaux de suivi dans un fichier mais lorsqu'une erreur est rencontrée, écrivez dans le même fichier, puis dans le fichier d'erreur et envoyez un e-mail en même temps ...


Number8: Découvrez les erreurs et les temps d'arrêt en utilisant des produits APM

TL; DR: Les produits de surveillance et de performance (a.k.a APM) évaluent de manière proactive votre base de code ou API afin qu'ils puissent automatiquement mettre en évidence les erreurs, les plantages et les parties lentes qui vous manquaient

Autrement: Vous pourriez dépenser beaucoup d'efforts pour mesurer les performances de l'API et les temps d'arrêt, probablement vous ne saurez jamais quelles sont vos parties de code les plus lentes dans un scénario réel et comment elles affectent l'UX

Citation de blog:"Segments de produits APM" (Du blog Yoni Goldberg)

"... Les produits APM constituent 3 segments majeurs:1. Surveillance du site Web ou de l'API - services externes qui surveillent constamment la disponibilité et les performances via des requêtes HTTP. Peut être configuré en quelques minutes. Voici quelques prétendants sélectionnés: Pingdom, Uptime Robot, et New Relic    2. Instrumentation de code - famille de produits qui nécessitent d'intégrer un agent dans l'application pour bénéficier de la détection de code lent, des statistiques d'exceptions, de la surveillance des performances et bien d'autres. Voici quelques prétendants sélectionnés: New Relic, App Dynamics    3. Tableau de bord de l'intelligence opérationnelle - Cette gamme de produits est axée sur la facilitation de l'équipe ops avec des métriques et un contenu organisé qui permet de rester facilement au top des performances des applications. Cela implique généralement l'agrégation de plusieurs sources d'informations (journaux d'application, journaux de base de données, journaux de serveurs, etc.) et le travail de conception de tableau de bord. Voici quelques candidats sélectionnés: Datadog, Splunk "


Ce qui précède est une version raccourcie - voir ici plus de bonnes pratiques et d'exemples


59
2017-09-05 20:11



Vous pouvez attraper des exceptions non interceptées, mais leur utilisation est limitée. Voir http://debuggable.com/posts/node-js-dealing-with-uncaught-exceptions:4c933d54-1428-443c-928d-4e1ecbdd56cb

monit, forever ou upstart peut être utilisé pour redémarrer le processus de noeud lorsqu'il se bloque. Un arrêt normal est le mieux que vous pouvez espérer (par exemple, enregistrer toutes les données en mémoire dans un gestionnaire d'exceptions non interceptées).


27
2018-04-10 19:45



domaines nodejs est la façon la plus à jour de gérer les erreurs dans nodejs. Les domaines peuvent capturer à la fois les erreurs / autres événements ainsi que les objets lancés traditionnellement. Les domaines fournissent également des fonctionnalités pour gérer les rappels avec une erreur transmise comme premier argument via la méthode d'interception.

Comme pour la gestion normale des erreurs try / catch-style, il est généralement préférable de lancer les erreurs lorsqu'elles se produisent et de bloquer les zones dans lesquelles vous souhaitez isoler les erreurs afin d'affecter le reste du code. Le moyen de "bloquer" ces zones est d'appeler domain.run avec une fonction comme un bloc de code isolé.

Dans le code synchrone, ce qui précède est suffisant - lorsqu'une erreur se produit, vous pouvez soit la laisser passer, soit l'attraper et la gérer, en annulant toutes les données dont vous avez besoin.

try {  
  //something
} catch(e) {
  // handle data reversion
  // probably log too
}

Lorsque l'erreur se produit dans un rappel asynchrone, vous devez être capable de gérer entièrement l'annulation des données (état partagé, données externes telles que les bases de données, etc.). OU vous devez définir quelque chose pour indiquer qu'une exception s'est produite. Si vous vous souciez de ce drapeau, vous devez attendre la fin du rappel.

var err = null;
var d = require('domain').create();
d.on('error', function(e) {
  err = e;
  // any additional error handling
}
d.run(function() { Fiber(function() {
  // do stuff
  var future = somethingAsynchronous();
  // more stuff

  future.wait(); // here we care about the error
  if(err != null) {
    // handle data reversion
    // probably log too
  }

})});

Une partie de ce code est moche, mais vous pouvez créer des motifs pour vous-même pour le rendre plus joli, par exemple:

var specialDomain = specialDomain(function() {
  // do stuff
  var future = somethingAsynchronous();
  // more stuff

  future.wait(); // here we care about the error
  if(specialDomain.error()) {
    // handle data reversion
    // probably log too
  } 
}, function() { // "catch"
  // any additional error handling
});

MISE À JOUR (2013-09):

Ci-dessus, j'utilise un futur qui implique sémantique des fibres, ce qui vous permet d'attendre sur les contrats à terme en ligne. Cela vous permet en fait d'utiliser des blocs try-catch traditionnels pour tout - Ce que je trouve être le meilleur moyen d'y aller. Cependant, vous ne pouvez pas toujours le faire (c'est-à-dire dans le navigateur) ...

Il y a aussi des contrats à terme qui ne nécessitent pas de sémantique des fibres (qui fonctionnent ensuite avec le JavaScript normal de la navigation). Ceux-ci peuvent être appelés des contrats à terme, des promesses, ou des différés (je me référerai juste aux contrats à terme d'ici). Les bibliothèques de contrats à terme en JavaScript simples permettent aux erreurs de se propager entre les contrats à terme. Seules certaines de ces bibliothèques permettent de gérer correctement tout futur projeté.

Un exemple:

returnsAFuture().then(function() {
  console.log('1')
  return doSomething() // also returns a future

}).then(function() {
  console.log('2')
  throw Error("oops an error was thrown")

}).then(function() {
  console.log('3')

}).catch(function(exception) {
  console.log('handler')
  // handle the exception
}).done()

Cela imite un try-catch normal, même si les pièces sont asynchrones. Cela imprimerait:

1
2
handler

Notez qu'il n'imprime pas '3' car une exception a été levée qui interrompt ce flux.

Jetez un oeil aux promesses bluebird:

Notez que je n'ai pas trouvé beaucoup d'autres bibliothèques qui traitent correctement les exceptions levées. Par exemple, jQuery est différée - le gestionnaire "fail" ne verra jamais l'exception renvoyée par un gestionnaire "then", ce qui à mon avis est un briseur d'affaire.


15
2018-01-10 00:16



J'ai écrit à ce sujet récemment à http://snmaynard.com/2012/12/21/node-error-handling/. Une nouvelle fonctionnalité de noeud dans la version 0.8 est les domaines et vous permet de combiner toutes les formes de gestion des erreurs dans un formulaire de gestion plus facile. Vous pouvez lire à leur sujet dans mon message.

Vous pouvez également utiliser quelque chose comme Bugsnag pour suivre vos exceptions non interceptées et être averti par e-mail, chatroom ou avoir un ticket créé pour une exception non capturée (je suis le co-fondateur de Bugsnag).


11
2017-09-11 14:26



Un exemple d'utilisation d'un try-catch peut être approprié lorsque vous utilisez une boucle forEach. Il est synchrone mais, en même temps, vous ne pouvez pas utiliser une instruction return dans la portée interne. Au lieu de cela, une approche try and catch peut être utilisée pour renvoyer un objet Error dans la portée appropriée. Considérer:

function processArray() {
    try { 
       [1, 2, 3].forEach(function() { throw new Error('exception'); }); 
    } catch (e) { 
       return e; 
    }
}

C'est une combinaison des approches décrites par @balupton ci-dessus.


4
2018-06-12 16:35



Je voudrais juste ajouter que Bibliothèque Step.js vous aide à gérer les exceptions en les transmettant toujours à la fonction de l'étape suivante. Vous pouvez donc avoir comme dernière étape une fonction qui vérifie les éventuelles erreurs dans les étapes précédentes. Cette approche peut grandement simplifier la gestion des erreurs.

Voici une citation de la page github:

toutes les exceptions levées sont capturées et transmises comme le premier argument à   la prochaine fonction. Tant que vous n'imprimez pas les fonctions de rappel en ligne   vos fonctions principales cela empêche là d'être jamais non pris   des exceptions. Ceci est très important pour les serveurs node.JS à exécution longue   car une seule exception non interceptée peut entraîner la panne de tout le serveur.

De plus, vous pouvez utiliser Step pour contrôler l'exécution des scripts afin d'avoir une section de nettoyage comme dernière étape. Par exemple, si vous voulez écrire un script de construction dans Node et indiquer combien de temps il a fallu pour écrire, la dernière étape peut le faire (plutôt que d'essayer de déterrer le dernier rappel).


3
2017-09-10 04:15



Après avoir lu ce billet il y a quelque temps, je me demandais s'il était sûr d'utiliser des domaines pour la gestion des exceptions au niveau des API / fonctions. Je voulais les utiliser pour simplifier le code de gestion des exceptions dans chaque fonction asynchrone que j'ai écrite. Mon souci était que l'utilisation d'un nouveau domaine pour chaque fonction introduirait des frais généraux importants. Mes devoirs semblent indiquer que les frais généraux sont minimes et que les performances sont meilleures dans les domaines que dans certaines situations.

http://www.lighthouselogic.com/#/using-a-new-domain-for-each-async-function-in-node/


2
2017-09-06 13:01