Node.js最佳实践异常处理

Node.js最佳实践异常处理,node.js,exception-handling,serverside-javascript,Node.js,Exception Handling,Serverside Javascript,几天前我刚开始试用node.js。我意识到,每当我的程序中出现未处理的异常时,节点就会终止。这与我接触过的普通服务器容器不同,在这里,当发生未处理的异常时,只有工作线程死亡,并且容器仍然能够接收请求。这引发了几个问题: process.on('uncaughtException')是防止它的唯一有效方法吗 process.on('uncaughtException')是否也会在异步进程执行期间捕获未处理的异常 是否有一个已经构建的模块(如发送电子邮件或写入文件),我可以在未捕获的异常情况下利用

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

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

如果有任何指针/文章能够向我展示node.js中处理未捕获异常的常见最佳实践,我将不胜感激。您可以捕获未捕获异常,但它的用途有限。看

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

更新: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)
        }
    })
    
  • 对于代码,如果错误可能发生在任何地方,则触发以下命令,而不是抛出错误:

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

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

  • 如果我们知道发生错误的地方是同步代码,并且出于任何原因不能使用域(可能是旧版本的节点),我们可以使用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/wait
    本机编写异步函数

    使用
    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
    侦听器使应用程序不会崩溃(但是这样做会使应用程序处于崩溃状态):


我只想添加一个功能,它通过始终将异常传递给下一步函数来帮助您处理异常。因此,最后一步可以使用一个函数来检查前面任何步骤中的错误。这种方法可以大大简化错误处理

以下是github页面中的一段引用:

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


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

如果您想在Ubuntu(Upstart)中使用服务:

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

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

这是上面@balupton所描述的方法的组合。

我最近在上一篇文章中写到了这一点。版本0.8中节点的一个新功能是域,允许您将所有错误处理形式合并到一个更易于管理的表单中。你可以在我的帖子里读到

您还可以使用类似的工具跟踪未捕获的异常,并通过电子邮件、聊天室或为未捕获的异常创建罚单(我是Bugsnag的联合创始人)。

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

与正常的try/catch样式错误处理一样,
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
}
// 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
function processArray() {
    try { 
       [1, 2, 3].forEach(function() { throw new Error('exception'); }); 
    } catch (e) { 
       return e; 
    }
}
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
});
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()
1
2
handler
var log = bunyan.createLogger({
  name: 'myapp',
  streams: [
    {
      level: 'error',
      path: '/var/tmp/myapp-error.log'  // log ERROR to this file
    }
  ]
});
var raygunClient = new raygun.Client().init({ apiKey: 'your API key' });
raygunClient.send(theError);
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){ 
                    ...
                });
            });
        });
    });
});
    //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?");
    //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);
});
//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);
    });
});
//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;
 }
//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' });
  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,
    }
  },