web-dev-qa-db-fra.com

Node.js Traitement des exceptions selon les meilleures pratiques

Je viens de commencer à essayer node.js il y a quelques jours. J'ai réalisé que le Node est terminé chaque fois que j'ai une exception non gérée dans mon programme. Cela diffère 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 où le conteneur pourrait toujours recevoir la demande. Cela soulève quelques questions:

  • Est-ce que process.on('uncaughtException') est le seul moyen efficace de s'en prémunir?
  • process.on('uncaughtException') interceptera-t-il également l'exception non gérée lors de l'exécution de processus asynchrones?
  • Existe-t-il un module déjà construit (tel que l’envoi de courrier électronique ou l’écriture dans un fichier) que je pourrais utiliser en cas d’exceptions non prises?

J'apprécierais tout pointeur/article qui me montrerait les meilleures pratiques communes en matière de gestion des exceptions non capturées dans node.js

724
momo

Mise à jour: Joyent a maintenant son propre guide . Les informations suivantes sont plus d'un résumé:

En toute sécurité "jeter" des erreurs

Idéalement, nous aimerions éviter autant que possible les erreurs non interceptées. En tant que telles, au lieu de générer l'erreur, nous pouvons au contraire "émettre" l'erreur en toute sécurité 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 survient, err est l'erreur, si une erreur ne se produit pas, alors err est null. Tous les autres arguments suivent l'argument err:

    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 le code événementiel , où l'erreur peut se produire n'importe où, au lieu de renvoyer l'erreur, déclenchez l'événement error à 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" les erreurs

Parfois, cependant, il peut encore y avoir du 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 le détectons pas correctement. Selon notre architecture de code, nous pouvons utiliser l'une des méthodes suivantes pour l'attraper:

  • Lorsque 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 le code synchrone est à l'origine de l'erreur et que, pour une raison quelconque, nous ne pouvons pas utiliser de domaines (peut-être une ancienne version du nœud), 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)
    }
    

    Toutefois, veillez à ne pas utiliser try...catch dans un code asynchrone, car une erreur généré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
    }
    

    Si vous souhaitez utiliser try..catch conjointement avec du code asynchrone, lorsque vous exécutez Node 7.4 ou version ultérieure, vous pouvez utiliser async/await de manière native pour écrire vos fonctions asynchrones.

    Une autre chose à laquelle il faut faire attention avec try...catch est le risque de placer votre rappel d'achèvement dans l'instruction try comme suit:

    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
    }
    

    Il est très facile d’acquérir ce code à mesure que 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); Dans les langues qui permettent un threading approprié au lieu du style de machine d'événements asynchrone de JavaScript, le problème est moins grave.

  • Enfin, dans le cas où une erreur non interceptée se produirait dans un endroit qui n'était pas encapsulé dans un domaine ou dans une instruction try catch, nous pouvons faire en sorte que notre application ne plante pas en utilisant l'écouteur uncaughtException (ce qui peut toutefois placer l'application dans - état inconn ):

    // 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
    
713
balupton

Vous trouverez ci-dessous un résumé et une synthèse de nombreuses sources différentes sur ce sujet, notamment un exemple de code et des citations tirées de certains blogs. La liste complète des meilleures pratiques peut être trouvée ici


Meilleures pratiques de traitement des erreurs Node.JS


Number1: Utiliser les promesses pour la gestion des erreurs asynchrones

TL; DR: Le traitement des erreurs asynchrones dans le style de rappel est probablement le moyen le plus rapide d’enfer (a.k.a la pyramide de Doom). Le meilleur cadeau que vous pouvez offrir à votre code utilise plutôt une bibliothèque de promesses réputée qui fournit une syntaxe de code très compacte et familière, telle que try-catch

Sinon: Le style de rappel de Node.JS, fonction (err, réponse), est un moyen prometteur de code non maintenable en raison de la combinaison de la gestion des erreurs avec le code occasionnel, des motifs d'imbrication excessifs et de codage maladroits

Exemple de code - bien

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

exemple de code anti-pattern - traitement des erreurs de style de rappel

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){ 
                    ...
                });
            });
        });
    });
});

Citation de blog: "Nous avons un problème avec les promesses" (Du blog pouchdb, classé 11 pour les mots-clés "Node Promises")

"… Et en fait, les rappels ont un effet encore plus sinistre: ils nous privent de la pile, ce que nous prenons généralement pour acquis dans les langages de programmation. Écrire du code sans pile est un peu comme conduire une voiture sans pédale de frein: vous ne réalisez pas à quel point vous en avez vraiment besoin, jusqu'à ce que vous l'atteigniez et que ce ne soit plus le cas. Le ​​but des promesses est de nous redonner les fondamentaux de la langue que nous avons perdus lorsque nous sommes devenus async: retour, lancer et Mais il faut savoir utiliser les promesses correctement pour en tirer parti. "


Numéro 2: Utiliser uniquement l'objet d'erreur intégré

TL; DR: Il est assez courant de voir du code qui génère des erreurs sous forme de chaîne ou sous forme de type personnalisé - cela complique la logique de traitement des erreurs et l'interopérabilité entre les modules. Que vous refusiez une promesse, leviez une exception ou émettiez une erreur, l'utilisation de l'objet d'erreur intégré à Node.JS augmente l'uniformité et évite la perte d'informations d'erreur.

Sinon: Lors de l'exécution d'un module, il est beaucoup plus difficile de raisonner sur le type d'erreur renvoyé - il est beaucoup plus difficile de raisonner sur l'exception à venir et de la gérer. Même utile, l'utilisation de types personnalisés pour décrire les erreurs peut entraîner la perte d'informations d'erreur critiques telles que la trace de pile!

Exemple de code - bien faire les choses

    //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?");

Citation de blog: "Une chaîne n'est pas une erreur" (D'après le blog devthought, classé 6 pour les mots-clés "Objet d'erreur Node.JS")

"… le fait de passer une chaîne au lieu d'une erreur entraîne une interopérabilité réduite entre les modules. Il rompt les contrats avec les API susceptibles d'effectuer des contrôles d'erreur ou de vouloir en savoir plus sur l'erreur. Les objets d'erreur, tels que nous verrons, avoir des propriétés très intéressantes dans les moteurs JavaScript modernes en plus de garder le message passé au constructeur .. "


Numéro 3: Distinguer les erreurs opérationnelles par rapport aux erreurs de programmation

TL; DR: Les erreurs d’opération (l’API, par exemple, a reçu une entrée non valide) font référence à des cas connus où l’impact de l’erreur est parfaitement compris et peut être traité de manière réfléchie. Par ailleurs, une erreur du programmeur (par exemple, essayer de lire une variable non définie) fait référence à des échecs de code inconnus nécessitant le redémarrage en douceur de l’application.

Sinon: Vous pouvez toujours redémarrer l'application lorsqu'une erreur apparaît, mais pourquoi laisser environ 5000 utilisateurs en ligne inutilisés à cause d'une erreur mineure et prédite (erreur opérationnelle)? l'inverse n'est pas non plus idéal - garder l'application active lorsqu'un problème inconnu (erreur du programmeur) s'est 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 - bien faire les choses

    //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 - marquant 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" (extrait du blog débogable, classé 3 pour les mots-clés "Node.JS exception non capturée")

"... De par la nature de son fonctionnement en JavaScript, il n’existe pratiquement aucun moyen de" reprendre là où vous en étiez "en toute sécurité, sans fuite de références ni création d’un autre état de fragilité non défini. Pour réagir à une erreur renvoyée, vous devez arrêter le processus. Bien sûr, sur un serveur Web normal, de nombreuses connexions peuvent être ouvertes et il n'est pas raisonnable de les fermer brutalement car une erreur a été déclenchée par quelqu'un La meilleure approche consiste à envoyer une réponse d'erreur à la demande qui a provoqué l'erreur, tout en laissant les autres terminer à l'heure normale, et d'arrêter l'écoute des nouvelles demandes de ce travailleur "


Number4: Traiter les erreurs de manière centralisée, via mais pas dans le middleware

TL; DR: La logique de traitement des erreurs, telle que le courrier adressé à l'administrateur et la consignation, doit être encapsulée dans un objet dédié et centralisé que tous les terminaux (par exemple, le middleware Express, les tâches cron, les tests d'unité) appellent. l'erreur entre.

Sinon: Ne pas gérer les erreurs au même endroit entraînera une duplication du code et probablement des erreurs mal traité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);
    });
});

Blog quote: "Parfois, les niveaux inférieurs ne peuvent rien faire d’utile si ce n’est propager l’erreur à leur interlocuteur" (extrait du blog Joyent, classé 1 pour les mots-clés "Traitement des erreurs Node.JS")

"… Vous pouvez finir par gérer la même erreur à plusieurs niveaux de la pile. Cela se produit lorsque des niveaux inférieurs ne peuvent rien faire d'utile, sauf de propager l'erreur à leur appelant, qui propage l'erreur à son appelant, etc. seul l'appelant de premier niveau sait quelle est la réponse appropriée, qu'il s'agisse de relancer l'opération, de signaler une erreur à l'utilisateur ou de quelque chose d'autre. Mais cela ne signifie pas que vous devriez essayer de signaler toutes les erreurs à un seul utilisateur de 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 avec Swagger

TL; DR: Informez vos appelants de l'API des erreurs susceptibles de leur être renvoyées afin qu'ils puissent les traiter de manière réfléchie sans planter. Cela se fait généralement avec les frameworks de documentation de l'API REST comme Swagger

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

Blog quote: "Vous devez dire à vos appelants quelles erreurs peuvent survenir" (extrait du blog Joyent, classé 1 pour les mots-clés "Enregistrement de Node.JS")

… Nous avons parlé de la gestion des erreurs, mais lorsque vous écrivez une nouvelle fonction, comment envoyez-vous des erreurs au code qui a appelé votre fonction? … Si vous ne savez pas quelles erreurs peuvent se produire ou ce qu’elles signifient, 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’elles représentent.


Numéro 6: Fermez le processus avec grâce lorsqu'un étranger arrive en ville

TL; DR: Quand une erreur inconnue se produit (une erreur de développeur, voir la meilleure pratique n ° 3) - il existe une incertitude quant à la santé de l'application. Une pratique courante suggère de relancer le processus avec précaution en utilisant un outil de "redémarrage" comme Forever et PM2.

Sinon: Lorsqu'une exception inconnue est interceptée, un objet peut être dans un état défectueux (par exemple, un émetteur d'événement utilisé de manière globale et ne déclenchant plus d'événements en raison d'une défaillance interne) et toutes les demandes futures peuvent échouer. ou se comporter follement

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;
 }

Blog quote: "Il existe trois écoles de pensée sur la gestion des erreurs" (extrait du blog jsrecipes)

… Il existe principalement trois écoles de pensée sur la gestion des erreurs: 1. Laissez l’application planter et redémarrez-la. 2. Traiter toutes les erreurs possibles et ne jamais planter. . 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 matures, tels que Winston, Bunyan ou Log4J, accélérera la détection et la compréhension des erreurs. Alors oubliez console.log.

Sinon: Parcourir les fichiers console.logs ou manuellement dans un fichier texte compliqué sans interroger les outils ni afficher les journaux de manière appropriée peut vous occuper au travail jusqu'à tard

Exemple de code - Enregistreur 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' });

Blog quote: "Permet d'identifier quelques exigences (pour un enregistreur):" (extrait du blog strongblog)

… Permet d'identifier quelques exigences (pour un enregistreur): 1. Horodatage de chaque ligne de journal. Celui-ci est assez explicite - vous devriez être capable de dire quand chaque entrée de journal a eu lieu. 2. Le format de la journalisation doit être facilement assimilable par les humains et les machines. 3. Permet plusieurs flux de destination configurables. Par exemple, vous écrivez peut-être des journaux de suivi dans un fichier, mais en cas d'erreur, écrivez dans le même fichier, puis dans le fichier d'erreur et envoyez un courrier électronique en même temps…


Number8: Détecter les erreurs et les temps d'arrêt à l'aide des produits APM

TL; DR: Produits de contrôle et de performances (a.k.a APM) évaluent de manière proactive votre base de code ou votre API afin qu'ils puissent automatiquement mettre en évidence les erreurs, les plantages et les ralentissements qui vous manquaient.

Sinon: Vous pourriez consacrer beaucoup d’efforts à la mesure des performances et des temps d’API des API, vous ne saurez probablement jamais quelles sont les parties de code les plus lentes dans un scénario réel et comment elles affectent l’UX

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

"… Les produits APM constituent 3 segments principaux: 1. Surveillance de sites Web ou d'API - services externes qui surveillent en permanence la disponibilité et les performances via des requêtes HTTP. Peut être configuré en quelques minutes. Voici quelques concurrents sélectionnés: Pingdom, Robot Uptime et nouvelle relique 2. Instrumentation de code - famille de produits nécessitant l'intégration d'un agent dans l'application pour bénéficier de la détection de code lente, des statistiques d'exceptions, du contrôle des performances et bien plus encore. : Nouvelle relique, dynamique des applications . Tableau de bord de l'intelligence opérationnelle - Cette ligne de produits vise à faciliter l'équipe des opérations grâce à des métriques et à un contenu soigné permettant de rester facilement au-dessus des performances des applications. agrégation de plusieurs sources d’information (journaux d’application, journaux de base de données, journal de serveur, etc.) et de travaux de conception de tableaux de bord initiaux.


Ceci est une version abrégée - voir plus de bonnes pratiques et d’exemples

82
Yonatan

Vous pouvez intercepter des exceptions non capturé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 peuvent être utilisés pour redémarrer un processus de noeud lorsqu'il se bloque. Il est préférable d’espérer un arrêt progressif (par exemple, enregistrez toutes les données en mémoire dans un gestionnaire d’exception non capturé).

29
nponeccop

domaines nodejs est la manière la plus récente de gérer les erreurs dans nodejs. Les domaines peuvent capturer les événements d'erreur/autres ainsi que les objets générés de manière traditionnelle. Les domaines fournissent également une fonctionnalité de traitement des rappels avec une erreur transmise comme premier argument via la méthode intercept.

Comme avec la gestion normale des erreurs try/catch-style, il est généralement préférable de supprimer les erreurs lorsqu'elles se produisent et de bloquer les zones où vous souhaitez isoler les erreurs des conséquences pour le reste du code. Le moyen de "bloquer" ces zones est d'appeler domain.run avec une fonction en tant que bloc de code isolé.

Dans le code synchrone, ce qui précède suffit: lorsqu'une erreur survient, vous la laissez passer, ou vous la récupérez et vous la manipulez, en inversant toutes les données que vous devez restaurer.

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

Lorsque l'erreur se produit lors d'un rappel asynchrone, vous devez être en mesure de gérer entièrement la restauration des données (état partagé, données externes telles que des bases de données, etc.). OR vous devez définir quelque chose pour indiquer qu'une exception s'est produite - si vous vous souciez de cet indicateur, 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 du code ci-dessus est moche, mais vous pouvez créer des motifs 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
});

UPDATE (2013-09):

Ci-dessus, j'utilise un futur qui implique sémantique des fibres , ce qui vous permet d'attendre les futurs en ligne. Cela vous permet en fait d'utiliser les blocs try-catch traditionnels pour tout - ce qui, à mon avis, est la meilleure voie à suivre. Cependant, vous ne pouvez pas toujours le faire (c.-à-d. Dans le navigateur) ...

Il existe également des contrats à terme ne nécessitant pas de sémantique de fibres (qui fonctionnent ensuite avec JavaScript, le code de navigation normal). Ceux-ci peuvent être appelés contrats à terme, promesses ou différés (je ferai simplement référence aux contrats à terme à partir de maintenant). Les bibliothèques de futurs de Plain-old-JavaScript permettent de propager des erreurs entre les futurs. Seules certaines de ces bibliothèques permettent de gérer correctement tout futur jeté, alors méfiez-vous.

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. Il imprimerait:

1
2
handler

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

Jetez un coup d'oeil aux promesses de bluebird:

Notez que je n'ai pas trouvé beaucoup d'autres bibliothèques que celles-ci qui gèrent correctement les exceptions levées. jQuery est différé, par exemple, ne le fait pas - le gestionnaire "fail" ne ferait jamais passer l'exception à un gestionnaire "then", ce qui, à mon avis, est un facteur décisif.

14
B T

J'ai écrit à ce sujet récemment à l'adresse http://snmaynard.com/2012/12/21/node-error-handling/ . Les domaines sont une nouvelle fonctionnalité du noeud dans la version 0.8: ils vous permettent de combiner toutes les formes de traitement des erreurs en un seul formulaire plus simple à gérer. Vous pouvez lire à leur sujet dans mon post.

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

12
Simon Maynard

L'utilisation d'un try-catch peut être appropriée, par exemple, lors de l'utilisation d'une boucle forEach. Il est synchrone, mais vous ne pouvez pas utiliser en même temps 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
Michael Yagudaev

Je voudrais juste ajouter que bibliothèque Step.js vous aide à gérer les exceptions en les passant toujours à la fonction step suivante. Par conséquent, vous pouvez avoir comme dernière étape une fonction qui vérifie les erreurs dans les étapes précédentes. Cette approche peut grandement simplifier la gestion des erreurs.

Ci-dessous une citation de la page github:

toutes les exceptions levées sont interceptées et transmises en tant que premier argument à la fonction suivante. Tant que vous n'imbriquerez pas de fonctions de rappel dans vos fonctions principales, cela empêchera toute exception non interceptée. Ceci est très important pour les serveurs node.JS qui fonctionnent longtemps, car une seule exception non interceptée peut mettre tout le serveur hors service.

En outre, vous pouvez utiliser Step pour contrôler l’exécution des scripts afin d’avoir une section de nettoyage en dernier lieu. Par exemple, si vous souhaitez écrire un script de construction dans Node et indiquer le temps qu'il a fallu pour l'écrire, la dernière étape peut le faire (plutôt que d'essayer de creuser le dernier rappel).

3
Michael Yagudaev

Après avoir lu cet article il y a quelque temps, je me demandais s'il était sûr d'utiliser des domaines pour la gestion des exceptions au niveau de l'API/de la fonction. Je souhaitais les utiliser pour simplifier le code de gestion des exceptions dans chacune des fonctions asynchrones que j'ai écrites. Mon inquiétude était que l'utilisation d'un nouveau domaine pour chaque fonction entraînerait des frais généraux importants. Mes devoirs semblent indiquer que les frais généraux sont minimes et que les performances sont en réalité meilleures avec les domaines qu'avec try catch dans certaines situations.

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

2
Sudsy

Nous avons très bien discuté de la résolution des erreurs, mais rappelez-vous de les consigner quelque part afin de pouvoir les visualiser et les corriger.

Bunyan est un framework de journalisation populaire pour NodeJS. Il prend en charge l'écriture sur plusieurs emplacements de sortie, ce qui le rend utile pour le débogage local, tant que vous évitez console.log. Dans le gestionnaire d'erreurs de votre domaine, vous pouvez cracher l'erreur dans un fichier journal.

var log = bunyan.createLogger({
  name: 'myapp',
  streams: [
    {
      level: 'error',
      path: '/var/tmp/myapp-error.log'  // log ERROR to this file
    }
  ]
});

Cela peut prendre beaucoup de temps si vous avez beaucoup d'erreurs et/ou de serveurs à vérifier, il serait donc intéressant de se pencher sur un outil tel que Raygun (disclaimer, je travaille chez Raygun) pour regrouper les erreurs - ou les utiliser ensemble. Si vous avez décidé d’utiliser Raygun en tant qu’outil, sa configuration est également assez simple.

var raygunClient = new raygun.Client().init({ apiKey: 'your API key' });
raygunClient.send(theError);

Croisée avec l'utilisation d'un outil comme PM2 ou Forever, votre application devrait pouvoir se bloquer, déconnecter ce qui s'est passé et redémarrer sans aucun problème majeur.

1
K. Craven

Si vous souhaitez utiliser les services dans Ubuntu (Upstart): Nœud en tant que service dans Ubuntu 11.04 avec upstart, monit et forever.js

1
Cap