几天前我刚开始试用node.js。我已经意识到,每当我的程序中出现未处理的异常时,节点就会终止。这与我所接触的正常服务器容器不同,在正常服务器容器中,当发生未处理的异常时,只有工作线程死亡,并且容器仍然能够接收请求。这引发了几个问题:

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

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


当前回答

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

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

其他回答

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

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

更新:Joyent现在有了自己的指南。以下信息更为概括:

安全地“抛出”错误

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

对于同步代码,如果发生错误,则返回错误://将除法器定义为同步函数var divideSync=函数(x,y){//如果错误条件?如果(y===0){//通过返回错误安全地“抛出”错误return new Error(“不能被零除”)}其他{//未发生错误,继续返回x/y}}//除以4/2var结果=分频同步(4,2)//是否发生错误?if(错误的结果实例){//安全地处理错误console.log('4/2=err',结果)}其他{//未发生错误,继续console.log('4/2='+结果)}//除以4/0结果=分频同步(4,0)//是否发生错误?if(错误的结果实例){//安全地处理错误console.log('4/0=err',结果)}其他{//未发生错误,继续console.log('4/0='+result)}对于基于回调的(即异步)代码,回调的第一个参数是err,如果发生错误,则err为错误,如果没有发生错误,那么err为空。err参数后面有任何其他参数:var divide=函数(x,y,next){//如果错误条件?如果(y===0){//通过调用完成回调安全地“抛出”错误//第一个参数是错误next(新错误(“不能被零除”))}其他{//未发生错误,继续next(空,x/y)}}除法(4,2,函数(错误,结果){//是否发生错误?if(错误){//安全地处理错误console.log('4/2=err',err)}其他{//未发生错误,继续console.log('4/2='+结果)}})除法(4,0,函数(错误,结果){//是否发生错误?if(错误){//安全地处理错误console.log('4/0=err',err)}其他{//未发生错误,继续console.log('4/0='+result)}})对于事件代码,如果错误可能发生在任何地方,而不是抛出错误,则触发错误事件://确定我们的分频器事件发射器var events=require('事件')var Divider=函数(){events.EventEmitter.call(this)}require('util').inherits(除法器,events.EventEmitter)//添加除法函数除法器prototype.divide=函数(x,y){//如果错误条件?如果(y===0){//通过发出错误来安全地“抛出”错误var err=新错误(“不能被零除”)this.emit('error',err)}其他{//未发生错误,继续这个.发射('分割',x,y,x/y)}//链条返回此;}//创建除法器并侦听错误var除法器=新除法器()divider.on('error',函数(err){//安全地处理错误console.log(错误)})除法器.on(“除法”,函数(x,y,结果){console.log(x+'/'+y+'='+result)})//划分除法器。除法(4,2)。除法(4,0)

安全地“捕捉”错误

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

当我们知道错误发生的位置时,我们可以将该部分包装在node.js域中var d=require('domain').create()d.on('error',函数(err){//安全地处理错误console.log(错误)})//捕获此异步或同步代码块中未捕获的错误d.run(函数){//要捕获抛出错误的异步或同步代码var err=新错误('example')抛出错误})如果我们知道发生错误的地方是同步代码,并且由于任何原因不能使用域(可能是旧版本的节点),我们可以使用try-catch语句://捕获此同步代码块中未捕获的错误//try-catch语句仅适用于同步代码尝试{//我们想要捕获抛出错误的同步代码var err=新错误('example')抛出错误}捕获(错误){//安全地处理错误console.log(错误)}但是,请注意不要使用try。。。在异步代码中捕获,因为不会捕获异步抛出的错误:尝试{setTimeout(函数){var err=新错误('example')抛出错误}, 1000)}捕获(错误){//此处不会捕获示例错误。。。崩溃我们的应用程序//因此需要域}如果你真的想和…一起工作。。catch与异步代码结合使用,当运行Node 7.4或更高版本时,您可以在本机使用async/await来编写异步函数。还有一件事要小心尝试。。。catch是在try语句中包装完成回调的风险,如下所示:var divide=函数(x,y,next){//如果错误条件?如果(y===0){//通过调用完成回调安全地“抛出”错误//第一个参数是错误next(新错误(“不能被零除”))}其他{//未发生错误,继续next(空,x/y)}}var continueOther=函数(错误,结果){抛出新错误('elswhere has failed')}尝试{划分(4,2,继续其他地方)//^执行分割//continue其他地方将在try语句中}捕获(错误){console.log(错误堆栈)//^将输出“意外”结果:其他地方已失败}当您的代码变得更复杂时,这个gotcha非常容易做到。因此,最好使用域或返回错误,以避免(1)异步代码中未捕获的异常(2)您不希望执行的try-catch-catching执行。在允许正确线程而不是JavaScript异步事件机样式的语言中,这不是什么问题。最后,如果一个未捕获的错误发生在一个没有被包装在域或try-catch语句中的地方,我们可以使用uncaughtException侦听器使我们的应用程序不会崩溃(但是这样做会使应用程序处于未知状态)://捕获未包装在域或try-catch语句中的未捕获错误//不要在模块中使用,而只能在应用程序中使用,否则我们可能会绑定多个process.on('ncaughtException',函数(err){//安全地处理错误console.log(错误)})//发出未捕获错误的异步或同步代码var err=新错误('example')抛出错误

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

下面是github页面的引用:

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

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

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

​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或永久性工具的情况下,你的应用程序应该能够崩溃、注销发生的事情并重新启动,而不会出现任何重大问题。

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