Node.js最佳实践异常处理
几天前我刚开始试用node.js。我意识到,每当我的程序中出现未处理的异常时,节点就会终止。这与我接触过的普通服务器容器不同,在这里,当发生未处理的异常时,只有工作线程死亡,并且容器仍然能够接收请求。这引发了几个问题:Node.js最佳实践异常处理,node.js,exception-handling,serverside-javascript,Node.js,Exception Handling,Serverside Javascript,几天前我刚开始试用node.js。我意识到,每当我的程序中出现未处理的异常时,节点就会终止。这与我接触过的普通服务器容器不同,在这里,当发生未处理的异常时,只有工作线程死亡,并且容器仍然能够接收请求。这引发了几个问题: process.on('uncaughtException')是防止它的唯一有效方法吗 process.on('uncaughtException')是否也会在异步进程执行期间捕获未处理的异常 是否有一个已经构建的模块(如发送电子邮件或写入文件),我可以在未捕获的异常情况下利用
是防止它的唯一有效方法吗李>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 }
并使用异步代码,则在运行Node 7.4或更高版本时,您可以使用try..catch
本机编写异步函数 使用async/wait
时要注意的另一件事是在try…catch
语句中包装完成回调的风险,如下所示:try
当你的代码变得更复杂时,这很容易做到。因此,最好使用域或返回错误以避免(1)异步代码中未捕获的异常(2)您不希望执行的try-catch捕获执行。在允许正确线程而不是JavaScript异步事件机风格的语言中,这不是一个问题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 }
- 最后,如果未捕获错误发生在未包装在域或try-catch语句中的位置,我们可以通过使用
侦听器使应用程序不会崩溃(但是这样做会使应用程序处于崩溃状态):uncaughtException
此外,您可以使用步骤来控制脚本的执行,以便将清理部分作为最后一步。例如,如果您想在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,
}
},