【问题标题】:Node.js Best Practice Exception HandlingNode.js 最佳实践异常处理
【发布时间】:2011-11-10 18:01:05
【问题描述】:

我几天前才开始尝试 node.js。我意识到只要我的程序中有未处理的异常,节点就会终止。这与我接触过的普通服务器容器不同,当发生未处理的异常并且容器仍然能够接收请求时,只有工作线程死亡。这提出了几个问题:

  • process.on('uncaughtException')是唯一有效的防范方法吗?
  • process.on('uncaughtException') 是否也会在异步进程执行期间捕获未处理的异常?
  • 是否有一个已经构建的模块(例如发送电子邮件或写入文件)可以在未捕获的异常情况下使用?

如果有任何指针/文章向我展示在 node.js 中处理未捕获异常的常见最佳实践,我将不胜感激

【问题讨论】:

  • 不应发生未捕获的异常。如果他们确实使用了在崩溃时重新启动整个应用程序的程序(nodemon、forever、supervisor)
  • 未捕获的异常总是会发生,除非您将每一段异步代码放入try .. catch,并检查您的所有库也都这样做了>
  • +1 Dan 起初我认为 你所有的库 有点夸张,因为你“只”需要在代码中包含所有“线程入口点”在尝试/捕获中。但仔细考虑一下,任何库都可能有一个setTimeoutsetInterval 或类似的东西埋在你的代码无法捕捉到的深处。
  • @EugeneBeresovksy Dan 是对的,但它并没有改变这样一个事实,即当发生 uncaughtExceptions 时,唯一安全的选择是重新启动应用程序。换句话说,您的应用程序崩溃了,您对此无能为力或应该采取的措施。如果您想做一些建设性的事情,请实施新的、仍处于试验阶段的 v0.8 域功能,以便您可以记录崩溃并向您的客户发送 5xx 响应。
  • @Dan 即使在 try .. catch 中包含所有回调函数也不能保证捕获错误。如果所需模块使用它自己的二进制文件,它们可能会不正常地崩溃。我在 phantomjs-node 上遇到过这种情况,在无法捕获的错误上失败(除非我要对所需的二进制文件进行某种过程检查,但我从未追求过)。

标签: node.js exception-handling serverside-javascript


【解决方案1】:

更新:Joyent 现在拥有their own guide。以下信息更多是摘要:

安全地“抛出”错误

理想情况下,我们希望尽可能避免未捕获的错误,因此,我们可以根据我们的代码架构,使用以下方法之一安全地“抛出”错误,而不是直接抛出错误:

  • 对于同步代码,如果发生错误,返回错误:

    // 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)
    }
    
  • 对于基于回调(即异步)的代码,回调的第一个参数是err,如果发生错误err是错误,如果没有发生错误则err是@987654332 @。 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)
        }
    })
    
  • 对于eventful 代码,错误可能发生在任何地方,而不是抛出错误,触发error event instead

    // 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)
    

安全地“捕捉”错误

但有时,可能仍有代码在某处抛出错误,如果我们没有安全地捕获它,可能会导致未捕获的异常和我们的应用程序潜在的崩溃。根据我们的代码架构,我们可以使用以下方法之一来捕获它:

  • 当我们知道错误发生在哪里时,我们可以将该部分包装在 node.js domain

    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
    })
    
  • 如果我们知道错误发生在同步代码的位置,并且由于某种原因不能使用域(可能是旧版本的节点),我们可以使用 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)
    }
    

    但是,请注意不要在异步代码中使用try...catch,因为不会捕获异步抛出的错误:

    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
    }
    

    如果您确实想将 try..catch 与异步代码一起使用,则在运行 Node 7.4 或更高版本时,您可以本机使用 async/await 来编写异步函数。

    try...catch 需要注意的另一件事是将完成回调包装在 try 语句中的风险,如下所示:

    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
    }
    

    随着您的代码变得越来越复杂,这个问题很容易做到。因此,最好使用域或返回错误以避免 (1) 异步代码中未捕获的异常 (2) try catch 捕获您不希望的执行。在允许正确线程而不是 JavaScript 的异步事件机器样式的语言中,这不是问题。

  • 最后,如果未捕获的错误发生在未包含在域或 try catch 语句中的地方,我们可以通过使用uncaughtException 侦听器使我们的应用程序不会崩溃(但是这样做可以使unknown state) 中的应用程序:

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

【讨论】:

  • 感谢 Raynos,已更新。你有解释try catch 邪恶的来源吗?因为我很想用证据来支持这一点。还修复了同步示例。
  • 这个答案不再有效。 Domains 解决了这个问题(node.js 推荐)
  • @balupton 应该抛出错误以进行错误处理。绝对不应该避免它们。它们没有任何东西会破坏应用程序或其他任何东西的执行。 Java 和大多数其他现代语言对异常都有很好的支持。在阅读了这里的一些被误导的帖子后,我唯一的结论是人们不太了解它们,所以害怕它们。害怕不确定的怀疑。至少 20 年前,这场辩论最终决定支持例外。
  • Now domains are deprecated by io.js: "这个模块正在等待弃用。一旦替换 API 完成,这个模块将被完全弃用......绝对必须具有以下功能的用户域提供可能暂时依赖它,但预计将来必须迁移到不同的解决方案。”
  • domain api is deprecated now?他们提到了一个替代 API - 谁知道它什么时候会出来,它会是什么样子?
【解决方案2】:

以下是对该主题的许多不同来源的总结和整理,包括代码示例和所选博客文章的引用。最佳实践的完整列表can be found here


Node.JS 错误处理的最佳实践


Number1:使用 Promise 进行异步错误处理

TL;DR: 以回调方式处理异步错误可能是通往地狱的最快方式(也就是末日金字塔)。你可以给你的代码最好的礼物是使用一个有信誉的 Promise 库,它提供了很多紧凑和熟悉的代码语法,比如 try-catch

否则: Node.JS 回调样式,function(err, response),由于错误处理与随意代码、过度嵌套和笨拙的编码混合在一起,是一种很有前途的不可维护代码的方法模式

代码示例 - 不错

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

代码示例反模式 - 回调样式错误处理

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

博客引用:“我们的承诺有问题” (来自博客 pouchdb,关键词“Node Promises”排名第 11)

"...事实上,回调做了一些更险恶的事情:它们剥夺了我们的堆栈,这是我们在编程语言中通常认为理所当然的事情。编写没有堆栈的代码很像开车没有刹车踏板:你不会意识到你多么需要它,直到你伸手去拿它却不在那里。promise 的全部意义在于把我们在异步时丢失的语言基础知识还给我们:return,throw,和堆栈。但是您必须知道如何正确使用 Promise 才能利用它们。"


Number2:仅使用内置的 Error 对象

TL;DR: 以字符串或自定义类型抛出错误的代码很常见——这使错误处理逻辑和模块之间的互操作性变得复杂。无论您拒绝承诺、抛出异常还是发出错误 - 使用 Node.JS 内置的 Error 对象都可以提高一致性并防止错误信息丢失

否则:在执行某些模块时,不确定会返回哪种类型的错误——这使得推理和处理即将到来的异常变得更加困难。更值得一提的是,使用自定义类型来描述错误可能会导致关键错误信息(如堆栈跟踪)丢失!

代码示例 - 正确操作

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

代码示例反模式

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

博客引用:“字符串不是错误” (来自博客devthought,关键词“Node.JS错误对象”排名第6)

"...传递字符串而不是错误会导致模块之间的互操作性降低。它会破坏与可能正在执行 instanceof 错误检查或想了解有关错误的更多信息的 API 的合同。错误正如我们将看到的,对象在现代 JavaScript 引擎中除了保存传递给构造函数的消息外,还具有非常有趣的属性。”


编号 3:区分操作错误和程序员错误

TL;DR: 操作错误(例如 API 接收到无效输入)是指已知情况,其中错误影响已被完全理解并且可以经过深思熟虑进行处理。另一方面,程序员错误(例如,试图读取未定义的变量)是指未知的代码故障,要求优雅地重新启动应用程序

否则:当出现错误时,您可能总是会重新启动应用程序,但为什么要让大约 5000 名在线用户因为一个小错误和预测错误(操作错误)而失望呢?相反的情况也不理想——在发生未知问题(程序员错误)时保持应用程序正常运行可能会导致无法预料的行为。区分两者允许机智地采取行动,并根据给定的上下文应用平衡的方法

代码示例 - 正确操作

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

代码示例 - 将错误标记为可操作(可信)

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

博客引用:“否则你会冒国家风险” (来自可调试的博客,关键词“Node.JS 未捕获异常”排名第 3)

"...由于 throw 在 JavaScript 中的工作原理,几乎没有任何方法可以安全地“从上次中断的地方继续”,而不会泄漏引用或创建其他类型的未定义的脆弱状态。响应抛出的错误最安全的方法是关闭进程。当然,在正常的 Web 服务器中,您可能打开了许多连接,并且突然关闭这些连接是不合理的,因为错误是由其他人触发。更好的方法是向触发错误的请求发送错误响应,同时让其他人在正常时间完成,并停止在该工作人员中侦听新请求”


Number4:集中处理错误,通过但不在中间件内

TL;DR: 错误处理逻辑,例如发给管理员的邮件和日志记录应该封装在一个专用且集中的对象中,所有端点(例如 Express 中间件、cron 作业、单元测试)出现错误时调用。

否则:不在一个地方处理错误会导致代码重复,并可能导致错误处理不当

代码示例 - 典型的错误流程

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

博客引述:“有时较低级别除了将错误传播给调用者外,无能为力” (来自 Joyent 博客,关键词“Node.JS 错误处理”排名第一)

"...您最终可能会在堆栈的多个级别处理相同的错误。这种情况发生在较低级别无法执行任何有用的操作时,只能将错误传播给它们的调用者,然后将错误传播给它的调用者,依此类推. 通常,只有顶级调用者知道适当的响应是什么,无论是重试操作、向用户报告错误还是其他。但这并不意味着您应该尝试将所有错误报告给单个顶级回调,因为该回调本身无法知道错误发生在什么上下文中”


编号 5:使用 Swagger 记录 API 错误

TL;DR:让您的 API 调用者知道可能会返回哪些错误,以便他们可以周到地处理这些错误而不会崩溃。这通常使用 REST API 文档框架(如 Swagger)来完成

否则: API 客户端可能决定崩溃并重新启动,只是因为他收到了一个他无法理解的错误。注意:API 的调用者可能是您(在微服务环境中非常典型)

博客引用:“你必须告诉你的调用者会发生什么错误” (来自 Joyent 博客,关键词“Node.JS logging”排名第一)

...我们已经讨论了如何处理错误,但是当您编写一个新函数时,如何将错误传递给调用您的函数的代码? …如果您不知道会发生什么错误或不知道它们的含义,那么您的程序不可能是正确的,除非是偶然的。所以如果你正在编写一个新函数,你必须告诉你的调用者会发生什么错误以及它们的含义


Number6:当陌生人来到镇上时优雅地关闭进程

TL;DR:当发生未知错误(开发人员错误,请参阅最佳实践 #3)时,应用程序的健康状况存在不确定性。一种常见的做法是建议使用 Forever 和 PM2 等“重启”工具小心地重启进程

否则:当捕获到不熟悉的异常时,某些对象可能处于故障状态(例如,全局使用的事件发射器,由于某些内部故障而不再触发事件)和所有未来请求可能会失败或行为异常

代码示例 - 判断是否崩溃

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

博客引述:“关于错误处理的三种思想流派” (来自博客 jsrecipes)

...关于错误处理主要有以下三种思路: 1. 让应用程序崩溃并重新启动。 2.处理所有可能的错误,永不崩溃。 3。两者之间的平衡方法


Number7:使用成熟的记录器来增加错误的可见性

TL;DR: 一套成熟的日志工具,如 Winston、Bunyan 或 Log4J,将加快错误发现和理解。所以忘记console.log吧。

否则:浏览console.logs 或手动浏览凌乱的文本文件而不使用查询工具或体面的日志查看器可能会让您忙于工作直到很晚

代码示例 - 运行中的 Winston 记录器

//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' });

博客引用:“让我们确定一些要求(对于记录器):” (来自博客strongblog)

...让我们确定一些要求(对于记录器): 1. 为每个日志行添加时间戳。这个很容易解释——你应该能够知道每个日志条目是什么时候发生的。 2. 日志格式应该易于人类和机器消化。 3. 允许多个可配置的目标流。例如,您可能正在将跟踪日志写入一个文件,但当遇到错误时,写入同一个文件,然后写入错误文件并同时发送电子邮件......


编号 8:使用 APM 产品发现错误和停机时间

TL;DR:监控和性能产品(又名 APM)主动评估您的代码库或 API,以便它们可以自动神奇地突出显示您遗漏的错误、崩溃和慢速部分

否则:您可能会花费大量精力来衡量 API 性能和停机时间,可能您永远不会知道在现实世界场景中哪些是您最慢的代码部分以及这些部分如何影响用户体验

博客引用:“APM 产品细分” (来自博客 Yoni Goldberg)

"...APM 产品由 3 个主要部分组成:1. 网站或 API 监控 - 通过 HTTP 请求持续监控正常运行时间和性能的外部服务。可以在几分钟内完成设置。以下是一些选定的竞争者: Pingdom、Uptime Robot 和 New Relic 2。代码检测—— 产品系列需要在应用程序中嵌入代理,以受益于慢代码检测、异常统计、性能监控等功能。以下是少数选定的竞争者:New Relic、App Dynamics 3。运营智能仪表板——这些产品线专注于通过指标和精选内容帮助运营团队轻松掌握应用程序性能。这通常涉及聚合多个信息源(应用程序日志、数据库日志、服务器日志等)和前期仪表板设计工作。以下是一些选定的竞争者:Datadog、Splunk"


以上为缩短版 - see here more best practices and examples

【讨论】:

    【解决方案3】:

    您可以捕获未捕获的异常,但它的用途有限。见http://debuggable.com/posts/node-js-dealing-with-uncaught-exceptions:4c933d54-1428-443c-928d-4e1ecbdd56cb

    monitforeverupstart 可用于在节点进程崩溃时重新启动它。优雅的关闭是您所希望的最好的方式(例如,将所有内存中的数据保存在未捕获的异常处理程序中)。

    【讨论】:

    • +1 该链接很有用,谢谢。我还在寻找node.js上下文中“优雅重启”的最佳实践和意义
    • 我在这种情况下对“优雅重启”的理解基本上就是 nponeccop 所建议的:让进程死掉,然后让所有正在运行的进程重新启动它。
    • 非常感谢您提供的链接!真的很有用!
    • 这是一个很好的答案。但是,我不同意在您的第一个示例中返回错误。返回Error 会使返回值变得多态,从而不必要地混淆函数的语义。此外,在 JavaScript 中已经通过给出 Infinity-InfinityNaN 来处理除以 0 的值,其中 typeof === 'number'。可以使用!isFinite(value) 检查它们。一般来说,我建议永远不要从函数返回错误。在代码易读性和维护方面更好地抛出或返回具有一致语义的特殊非​​多态值。
    【解决方案4】:

    nodejs domains 是 nodejs 中处理错误的最新方法。域可以捕获错误/其他事件以及传统上抛出的对象。域还提供了处理回调的功能,错误是通过拦截方法作为第一个参数传递的。

    与正常的 try/catch 式错误处理一样,通常最好在错误发生时抛出错误,并阻止您希望隔离错误以免影响其余代码的区域。 “屏蔽”这些区域的方法是调用 domain.run 函数作为隔离代码块。

    在同步代码中,以上内容就足够了 - 当发生错误时,您要么让它被抛出,要么捕获它并在那里处理,恢复您需要恢复的任何数据。

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

    当错误发生在异步回调中时,您要么需要能够完全处理数据的回滚(共享状态、数据库等外部数据等)。或者你必须设置一些东西来表明发生了异常 - 无论你关心那个标志,你都必须等待回调完成。

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

    上面的一些代码很丑,但你可以自己创建模式让它更漂亮,例如:

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

    更新(2013-09):

    在上面,我使用了暗示fibers semantics 的future,它允许您在线等待future。这实际上允许您对 everything 使用传统的 try-catch 块——我认为这是最好的方法。但是,您不能总是这样做(即在浏览器中)...

    也有不需要纤维语义的期货(然后可以使用普通的浏览器 JavaScript)。这些可以称为futures、promises 或deferreds(从这里我将只提到futures)。普通的旧 JavaScript 期货库允许在期货之间传播错误。只有其中一些库允许正确处理任何抛出的未来,所以要小心。

    一个例子:

    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()
    

    这模仿了正常的 try-catch,即使这些片段是异步的。它会打印:

    1
    2
    handler
    

    请注意,它不会打印“3”,因为引发了中断该流程的异常。

    看看 bluebird promises:

    请注意,除了可以正确处理抛出异常的库之外,我还没有找到许多其他库。例如,jQuery 的 deferred 不会 - “失败”处理程序永远不会让异常抛出一个 'then' 处理程序,在我看来这是一个交易破坏者。

    【讨论】:

    【解决方案5】:

    我最近在http://snmaynard.com/2012/12/21/node-error-handling/ 上写了这篇文章。 0.8 版本中节点的一个新特性是域,它允许您将所有形式的错误处理组合成一个更易于管理的形式。您可以在我的帖子中了解它们。

    您还可以使用Bugsnag 之类的东西来跟踪您未捕获的异常,并通过电子邮件、聊天室或为未捕获的异常创建票证(我是 Bugsnag 的联合创始人)。

    【讨论】:

    【解决方案6】:

    可能适合使用 try-catch 的一个实例是使用 forEach 循环时。它是同步的,但同时您不能只在内部范围内使用 return 语句。相反,可以使用 try and catch 方法在适当的范围内返回 Error 对象。考虑:

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

    它是上述@balupton 描述的方法的组合。

    【讨论】:

    • 一些开发人员建议使用 Rust 的 Result 概念来返回 OKFail,而不是抛出错误,当失败是已知的可能性。这使故障与意外错误分开。一个 JS 实现是 r-result
    • 这是一个应用范围的设计决策。我认为您返回错误的概念大致相同,并且易于上手(没有额外的依赖项),但不太明确(Result 让您痛苦地意识到何时可能需要处理失败)并且效率较低在那些不必要地构建堆栈的情况下。
    【解决方案7】:

    我想补充一点,Step.js library 通过始终将异常传递给下一步函数来帮助您处理异常。因此,您可以在最后一步使用一个函数来检查任何先前步骤中的任何错误。这种方法可以大大简化您的错误处理。

    以下是github页面的引用:

    任何抛出的异常都会被捕获并作为第一个参数传递给 下一个功能。只要你不内联嵌套回调函数 你的主要功能,这可以防止那里有任何未被抓住 例外。这对于长时间运行的 node.JS 服务器非常重要 因为一个未捕获的异常可能会导致整个服务器停机。

    此外,您可以使用 Step 来控制脚本的执行,以将清理部分作为最后一步。例如,如果你想在 Node 中编写一个构建脚本并报告编写需要多长时间,最后一步可以做到这一点(而不是试图挖掘最后一个回调)。

    【讨论】:

      【解决方案8】:

      这里已经很好地讨论了捕获错误,但值得记住将错误记录在某个地方,以便您可以查看它们并进行修复。

      ​Bunyan 是一个流行的 NodeJS 日志框架——它支持写到一堆不同的输出位置,这使得它对本地调试很有用,只要你避免使用 console.log。 ​​​ 在您的域的错误处理程序中,您可以将错误吐出到日志文件中。

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

      如果您有很多错误和/或要检查的服务器,这可能会很耗时,因此可能值得研究像 Raygun(免责声明,我在 Raygun 工作)这样的工具来将错误分组在一起 - 或同时使用它们. ​​​ 如果您决定使用 Raygun 作为工具,它也很容易设置

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

      ​ 与使用 PM2 或永远这样的工具交叉使用,您的应用应该能够崩溃、注销发生的事情并重新启动,而不会出现任何重大问题。

      【讨论】:

        【解决方案9】:

        前段时间阅读这篇文章后,我想知道在 api / 函数级别使用域进行异常处理是否安全。我想用它们来简化我编写的每个异步函数中的异常处理代码。我担心的是,为每个函数使用一个新域会带来很大的开销。我的作业似乎表明开销最小,并且在某些情况下,使用域的性能实际上比使用 try catch 更好。

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

        【讨论】:

          【解决方案10】:

          如果你想在 Ubuntu(Upstart) 中使用服务:Node as a service in Ubuntu 11.04 with upstart, monit and forever.js

          【讨论】:

            【解决方案11】:
              getCountryRegionData: (countryName, stateName) => {
                let countryData, stateData
            
                try {
                  countryData = countries.find(
                    country => country.countryName === countryName
                  )
                } catch (error) {
                  console.log(error.message)
                  return error.message
                }
            
                try {
                  stateData = countryData.regions.find(state => state.name === stateName)
                } catch (error) {
                  console.log(error.message)
                  return error.message
                }
            
                return {
                  countryName: countryData.countryName,
                  countryCode: countryData.countryShortCode,
                  stateName: stateData.name,
                  stateCode: stateData.shortCode,
                }
              },
            

            【讨论】:

              猜你喜欢
              • 2018-01-03
              • 1970-01-01
              • 2017-05-11
              • 2013-05-09
              • 1970-01-01
              • 2013-04-22
              • 1970-01-01
              相关资源
              最近更新 更多