Node.js最佳实践异常处理


755

几天前我才开始尝试使用node.js。我已经意识到,只要程序中有未处理的异常,Node就会终止。这与我所见过的普通服务器容器不同,在普通服务器容器中,当发生未处理的异常时,只有工作线程死亡,并且容器仍能够接收请求。这引起了一些问题:

  • process.on('uncaughtException')防范它的唯一有效途径?
  • process.on('uncaughtException')在异步过程执行期间是否还会捕获未处理的异常?
  • 是否存在已经构建的模块(例如发送电子邮件或写入文件),在未捕获的异常的情况下可以利用该模块?

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


11
未捕获的异常不应发生。如果他们确实使用了当程序崩溃时会重新启动整个应用程序的程序(nodemon,永远,主管)
Raynos 2011年

116
未捕获的异常可随时发生,除非你把一块您的异步代码里面的try .. catch,而且检查这也是完成你所有的库

13
+1 Dan起初,我认为您的所有库都有些夸张,因为您“仅”需要将所有“线程入口点”包装在try / catches中的代码中。但是仔细考虑一下,任何库都可能在深处埋藏了一个setTimeoutsetInterval多个东西,而您的代码无法捕获它们。
尤金·别列索夫斯基

8
@EugeneBeresovksy Dan是正确的,但它不会改变uncaughtExceptions发生时唯一安全的选择就是重启应用程序这一事实。换句话说,您的应用已崩溃,您对此无能为力。如果您想做一些有建设性的事情,请实施新的仍处于试验阶段的v0.8域功能,以便您可以记录崩溃并将5xx响应发送给客户端。
ostergaard 2013年

1
@Dan即使将所有回调函数都包含在try .. catch中,也不能保证捕获错误。如果必需的模块使用其自己的二进制文件,则它们可能会不正常地崩溃。我曾经在phantomjs-node上发生过这种情况,失败的原因是无法捕捉到的错误(除非我要对所需的二进制文件进行某种过程检查,但我从未尝试过这样做)。
Trindaz

Answers:


736

更新:Joyent现在有自己的指南。以下信息更多是摘要:

安全地“抛出”错误

理想情况下,我们希望尽可能避免未捕获的错误,因此,除了从字面上抛出错误外,我们还可以根据我们的代码体系结构使用以下方法之一安全地“抛出”错误:

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

    // 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则为null。其他任何参数都遵循以下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)
        }
    })
  • 对于事件代码,错误可能发生在任何地方,而不是引发错误,请触发error事件

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

    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)尝试捕获不需要的执行。在允许适当的线程而不是JavaScript的异步事件机器风格的语言中,这不是问题。

  • 最后,在未包裹域或try catch语句的地方发生未捕获的错误的情况下,我们可以使用uncaughtException侦听器使应用程序不崩溃(但是这样做会使应用程序处于未知状态)):

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

5
谢谢雷诺斯,更新。您是否有资料来说明...的弊端try catch?因为我很乐意提供证据支持。还修复了同步示例。
balupton 2011年

2
该答案不再有效。域解决了这个问题(由node.js推荐)
Gabriel Llamas

5
@balupton应该引发错误以进行错误处理。绝对不应避免使用它们。没有任何东西可以破坏应用程序或其他任何东西的执行。Java和大多数其他现代语言对异常都有很好的支持。在阅读了这里一些错误的帖子后,我唯一的结论是,人们对它们的理解不是很好,因此很害怕它们。恐惧不确定的怀疑。这场辩论是在至少20年前最终决定支持例外的。
enl8enmentnow

22
现在,io.js弃用了域:“ 该模块待弃用。一旦替换API完成,此模块将被弃用...绝对必须具有域提供的功能的用户暂时可能会依赖它,但是我们应该期望将来必须迁移到其他解决方案。”
蒂莫西·顾

5
域的API现在已经过时?他们提到了替换API-任何人都知道何时会发布它,以及它的外观?
UpTheCreek

95

以下是该主题的许多不同来源的摘要和精选,包括代码示例和部分博客文章的引文。最佳做法的完整列表可以在这里找到


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


编号1:使用诺言进行异步错误处理

TL; DR:以回调方式处理异步错误可能是通向地狱的最快方法(又名“厄运金字塔”)。您可以给代码提供的最好礼物是使用信誉良好的Promise库,该库提供了很多紧凑和熟悉的代码语法,例如try-catch

否则:由于错误处理与临时代码,过多的嵌套和笨拙的编码模式相结合,Node.JS回调样式,函数(错误,响应)是一种无法维护代码的有前途的方法

代码示例-好

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,关键字“节点承诺”排名11)

“……事实上,回调的作用更加险恶:它们剥夺了我们的堆栈,这在编程语言中通常是我们所理所当然的。没有堆栈的代码编写就像在没有刹车的情况下驾驶汽车:直到达到并没有达到目标时,才意识到自己有多急。诺言的全部目的是让我们恢复异步时丢失的语言基础:返回,抛出和堆栈。必须知道如何正确使用诺言才能利用它们。


2号:仅使用内置的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 error object”的排名为6)

“…传递字符串而不是错误会导致模块之间的互操作性降低。它破坏了与可能正在执行错误检查实例或想要了解更多有关错误的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服务器,你可能有很多连接打开,因为错误是由其他人触发时,它是不合理的突然关闭这些了。更好的方法是向触发错误的请求发送错误响应,同时让其他请求在正常时间内完成操作,并停止监听该工作进程中的新请求”


第四条:通过中间件而不是中间件集中处理错误

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错误处理”排名1)

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


5:使用Swagger记录文档API错误

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

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

博客引用: “您必须告诉调用者可能发生什么错误”(来自Joyent博客,关键字“ Node.JS logging”排名1)

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


6号:当一个陌生人来到小镇时,优雅地关闭该过程

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.两者之间的平衡方法


7号:使用成熟的记录器来提高错误可见性

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.允许多个可配置的目标流。例如,您可能将跟踪日志写入一个文件,但是遇到错误时,先写入同一文件,然后写入错误文件并同时发送电子邮件...


第八条:使用APM产品发现错误和停机时间

TL; DR:监视和性能产品(aka APM)主动评估您的代码库或API,以便它们可以自动突出显示您所缺少的错误,崩溃和缓慢的部分

否则:您可能会花费大量精力来衡量API性能和停机时间,可能永远不会知道在现实情况下,哪些是您最慢的代码部分,以及它们如何影响UX

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

“……APM产品包括3个主要部分:1.网站或API监视–外部服务通过HTTP请求不断监视正常运行时间和性能。可以在几分钟内设置。以下是一些竞争者:Pingdom,正常运行时间机器人和New Relic 2 。代码工具–需要在应用程序中嵌入代理才能受益的产品系列,其特点是缓慢的代码检测,异常统计信息,性能监控等,以下是一些选定的竞争者:New Relic,App Dynamics 3.运营情报仪表板–这些产品系列专注于为操作团队提供指标和精心设计的内容,以帮助轻松地保持应用程序性能的最高水平。这通常涉及汇总多种信息源(应用程序日志,数据库日志,服务器日志等)和前期仪表板设计工作。以下是一些选定的竞争者:Datadog,Splunk”


上面是简化版- 请在此处查看更多最佳做法和示例


30

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

monitforever或者upstart可用于在崩溃时重新启动节点进程。正常关机是您所希望的(例如,将所有内存数据保存在未捕获的异常处理程序中)。


4
+1链接非常有用,谢谢。我仍然在node.js的上下文中寻找最佳实践和“平稳重启”的含义
momo

在这种情况下,我对“正常重启”的理解本质上就是nponeccop所建议的:让进程死掉,让所有正在运行的进程重启。
Ilkka

非常感谢您提供的链接!真的很有用!
SatheeshJM 2012年

这是一个很好的答案。我不同意在第一个示例中返回错误。返回an Error会使返回值变为多态,从而不必要地混淆了函数的语义。此外,潜水由0已经在JavaScript中通过给予处理Infinity-InfinityNaN,值,其中typeof === 'number'。可以使用检查它们!isFinite(value)。通常,我建议不要从函数返回错误。就代码易读性和维护性而言,更好的方法是抛出或返回具有一致语义的特殊非​​多态值。
wprl 2015年


13

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

与正常的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):

上面,我使用了隐含fibre语义的future ,它使您可以在线等待future 。实际上,这使您可以对所有内容使用传统的try-catch块-我认为这是最好的方法。但是,您不能总是这样做(即在浏览器中)...

还有一些期货不需要纤维语义(然后可以使用普通的浏览器JavaScript)。这些可以称为期货,承诺或递延(从现在开始,我仅指期货)。普通的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”,因为引发了异常,从而中断了该流程。

看看蓝鸟的承诺:

请注意,除了可以正确处理引发的异常的库以外,我没有找到其他许多库。举例来说,jQuery不会延迟-“失败”处理程序永远不会将引发“ then”处理程序的异常抛出,在我看来,这是一个破坏交易的行为。


Javascript中正确的Promise规范称为Promises / A +。您可能会在此处看到实施列表:github.com/promises-aplus/promises-spec/blob/master/…。请注意,裸露的Promises / A +实际上是不可用的-Promises / A +仍然为图书馆解决了许多实际问题。但是,可以保证绝对必要的事情,例如您显示的错误传播,确定的执行顺序和堆栈溢出的安全性。
Esailija


11

我最近在http://snmaynard.com/2012/12/21/node-error-handling/上撰写了有关此内容的文章。域0.8版中节点的一项新功能是域,它使您可以将所有错误处理形式组合为一个更易于管理的形式。您可以在我的文章中阅读有关它们的信息。

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


2
域模块现已正式弃用。nodejs.org/api/domain.html
MattSidor '16

3

我只想补充一下,Step.js库通过始终将其传递给下一步函数来帮助您处理异常。因此,您可以在最后一步拥有一个功能,该功能可以检查前面任何步骤中的任何错误。这种方法可以大大简化您的错误处理。

以下是github页面的引文:

捕获所有抛出的异常,并将其作为第一个参数传递给下一个函数。只要您不将回调函数嵌套到主函数中,就可以防止任何未捕获的异常。这对于长时间运行的node.JS服务器非常重要,因为单个未捕获的异常会导致整个服务器宕机。

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


3

在使用forEach循环时,使用try-catch可能更合适。它是同步的,但同时不能只在内部作用域中使用return语句。相反,可以使用“尝试并捕获”方法来返回适当范围内的Error对象。考虑:

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

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


一些开发人员建议不要使用任何错误,而应该抛出Rust的Result概念,以在可能发生故障时返回OKFail。这样可以使故障与意外错误分开。此结果的一种JS实现是r-result
joeytwiddle

这是应用程序范围内的设计决策。我认为您的返回错误的概念大致相同,并且易于上手(没有任何额外的依赖关系),但是不太明确(结果使您痛苦地意识到何时可能需要处理故障),而在那些情况下,堆栈的效率较低不必要地构建。
joeytwiddle

1

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

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


1

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

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或永久使用PM2,您的应用程序都将能够崩溃,注销发生的事情并重新启动而不会出现任何重大问题。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.