Node.js 最佳实践异常处理
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7310521/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Node.js Best Practice Exception Handling
提问by momo
I just started trying out node.js a few days ago. I've realized that the Node is terminated whenever I have an unhandled exception in my program. This is different than the normal server container that I have been exposed to where only the Worker Thread dies when unhandled exceptions occur and the container would still be able to receive the request. This raises a few questions:
几天前我刚开始尝试 node.js。我意识到只要我的程序中有未处理的异常,节点就会终止。这与我接触过的普通服务器容器不同,其中只有 Worker Thread 在发生未处理的异常时死亡,并且容器仍然能够接收请求。这提出了几个问题:
- Is
process.on('uncaughtException')the only effective way to guard against it? - Will
process.on('uncaughtException')catch the unhandled exception during execution of asynchronous processes as well? - Is there a module that is already built (such as sending email or writing to a file) that I could leverage in the case of uncaught exceptions?
- 是
process.on('uncaughtException')唯一有效的防范方法吗? process.on('uncaughtException')在执行异步进程期间也会捕获未处理的异常吗?- 是否有一个已经构建的模块(例如发送电子邮件或写入文件),我可以在未捕获异常的情况下利用它?
I would appreciate any pointer/article that would show me the common best practices for handling uncaught exceptions in node.js
我将不胜感激任何能向我展示在 node.js 中处理未捕获异常的常见最佳实践的指针/文章
采纳答案by balupton
Update: Joyent now has their own guide. The following information is more of a summary:
更新:Joyent 现在有自己的指南。以下信息更像是一个摘要:
Safely "throwing" errors
安全地“抛出”错误
Ideally we'd like to avoid uncaught errors as much as possible, as such, instead of literally throwing the error, we can instead safely "throw" the error using one of the following methods depending on our code architecture:
理想情况下,我们希望尽可能避免未捕获的错误,因此,我们可以根据我们的代码架构使用以下方法之一安全地“抛出”错误,而不是直接抛出错误:
For synchronous code, if an error happens, return the error:
// 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) }For callback-based (ie. asynchronous) code, the first argument of the callback is
err, if an error happenserris the error, if an error doesn't happen thenerrisnull. Any other arguments follow theerrargument: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) } })For eventfulcode, where the error may happen anywhere, instead of throwing the error, fire the
errorevent 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)
对于同步代码,如果发生错误,则返回错误:
// 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)
Safely "catching" errors
安全地“捕捉”错误
Sometimes though, there may still be code that throws an error somewhere which can lead to an uncaught exception and a potential crash of our application if we don't catch it safely. Depending on our code architecture we can use one of the following methods to catch it:
但有时,可能仍然有代码在某处抛出错误,如果我们不安全地捕获它,可能会导致未捕获的异常和我们的应用程序潜在的崩溃。根据我们的代码架构,我们可以使用以下方法之一来捕获它:
When we know where the error is occurring, we can wrap that section in a 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 })If we know where the error is occurring is synchronous code, and for whatever reason can't use domains (perhaps old version of node), we can use the try catch statement:
// 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) }However, be careful not to use
try...catchin asynchronous code, as an asynchronously thrown error will not be caught: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 }If you do want to work with
try..catchin conjunction with asynchronous code, when running Node 7.4 or higher you can useasync/awaitnatively to write your asynchronous functions.Another thing to be careful about with
try...catchis the risk of wrapping your completion callback inside thetrystatement like so: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 }This gotcha is very easy to do as your code becomes more complex. As such, it is best to either use domains or to return errors to avoid (1) uncaught exceptions in asynchronous code (2) the try catch catching execution that you don't want it to. In languages that allow for proper threading instead of JavaScript's asynchronous event-machine style, this is less of an issue.
Finally, in the case where an uncaught error happens in a place that wasn't wrapped in a domain or a try catch statement, we can make our application not crash by using the
uncaughtExceptionlistener (however doing so can put the application in an 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
当我们知道错误发生在哪里时,我们可以将该部分包装在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) try catch 捕获您不希望的执行。在允许正确线程而不是 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
回答by Yonatan
Following is a summarization and curation from many different sources on this topic including code example and quotes from selected blog posts. The complete list of best practices can be found here
以下是针对此主题的许多不同来源的总结和整理,包括代码示例和来自选定博客文章的引用。可以在此处找到最佳实践的完整列表
Best practices of Node.JS error handling
Node.JS 错误处理最佳实践
Number1: Use promises for async error handling
Number1:使用 promise 进行异步错误处理
TL;DR:Handling async errors in callback style is probably the fastest way to hell (a.k.a the pyramid of doom). The best gift you can give to your code is using instead a reputable promise library which provides much compact and familiar code syntax like try-catch
TL;DR:以回调方式处理异步错误可能是最快的方法(也就是厄运金字塔)。你可以给你的代码最好的礼物是使用一个信誉良好的承诺库,它提供了很多紧凑和熟悉的代码语法,比如 try-catch
Otherwise:Node.JS callback style, function(err, response), is a promising way to un-maintainable code due to the mix of error handling with casual code, excessive nesting and awkward coding patterns
否则:由于错误处理与随意代码、过度嵌套和笨拙的编码模式的混合,Node.JS 回调风格、function(err, response) 是不可维护代码的一种有前途的方式
Code example - good
代码示例 - 好
doWork()
.then(doWork)
.then(doError)
.then(doWork)
.catch(errorHandler)
.then(verify);
code example anti pattern – callback style error handling
代码示例反模式 – 回调式错误处理
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){
...
});
});
});
});
});
Blog quote: "We have a problem with promises"(From the blog pouchdb, ranked 11 for the keywords "Node Promises")
博客引用:“我们的承诺有问题”(来自博客 pouchdb,关键词“节点承诺”排名第 11)
"…And in fact, callbacks do something even more sinister: they deprive us of the stack, which is something we usually take for granted in programming languages. Writing code without a stack is a lot like driving a car without a brake pedal: you don't realize how badly you need it, until you reach for it and it's not there. The whole point of promises is to give us back the language fundamentals we lost when we went async: return, throw, and the stack. But you have to know how to use promises correctly in order to take advantage of them."
“......事实上,回调做了一些更险恶的事情:它们剥夺了我们在编程语言中通常认为理所当然的堆栈。编写没有堆栈的代码很像在没有刹车踏板的情况下驾驶汽车:你不要意识到你有多需要它,直到你伸手去拿它,但它不在那里。承诺的全部意义是把我们在异步时失去的语言基础还给我们:返回、抛出和堆栈。但是你必须知道如何正确使用 Promise 才能利用它们。”
Number2: Use only the built-in Error object
Number2:仅使用内置的 Error 对象
TL;DR:It pretty common to see code that throws errors as string or as a custom type – this complicates the error handling logic and the interoperability between modules. Whether you reject a promise, throw exception or emit error – using Node.JS built-in Error object increases uniformity and prevents loss of error information
TL;DR:经常看到以字符串或自定义类型的形式抛出错误的代码——这使错误处理逻辑和模块之间的互操作性变得复杂。无论是拒绝承诺、抛出异常还是发出错误——使用 Node.JS 内置的 Error 对象可以提高一致性并防止错误信息丢失
Otherwise:When executing some module, being uncertain which type of errors come in return – makes it much harder to reason about the coming exception and handle it. Even worth, using custom types to describe errors might lead to loss of critical error information like the stack trace!
否则:在执行某个模块时,不确定返回的是哪种类型的错误 - 使得推断即将发生的异常并处理它变得更加困难。甚至值得,使用自定义类型来描述错误可能会导致关键错误信息(如堆栈跟踪)的丢失!
Code example - doing it right
代码示例 - 做对了
//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?"));
code example anti pattern
代码示例反模式
//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?");
Blog quote: "A string is not an error"(From the blog devthought, ranked 6 for the keywords “Node.JS error object”)
博客引用:“A string is not an error”(来自博客 devthought,关键字“Node.JS 错误对象”排名第 6)
"…passing a string instead of an error results in reduced interoperability between modules. It breaks contracts with APIs that might be performing instanceof Error checks, or that want to know more about the error. Error objects, as we'll see, have very interesting properties in modern JavaScript engines besides holding the message passed to the constructor.."
“……传递字符串而不是错误会导致模块之间的互操作性降低。它破坏了与可能正在执行错误检查实例或想要了解更多错误信息的 API 的契约。正如我们将看到的,错误对象具有非常除了保存传递给构造函数的消息之外,现代 JavaScript 引擎中还有一些有趣的属性……”
Number3: Distinguish operational vs programmer errors
编号 3:区分操作错误与程序员错误
TL;DR:Operations errors (e.g. API received an invalid input) refer to known cases where the error impact is fully understood and can be handled thoughtfully. On the other hand, programmer error (e.g. trying to read undefined variable) refers to unknown code failures that dictate to gracefully restart the application
TL;DR:操作错误(例如 API 接收到无效输入)是指已知情况,其中错误影响已被完全理解并可以周到处理。另一方面,程序员错误(例如尝试读取未定义的变量)是指未知的代码失败,这些错误要求优雅地重新启动应用程序
Otherwise:You may always restart the application when an error appear, but why letting ~5000 online users down because of a minor and predicted error (operational error)? the opposite is also not ideal – keeping the application up when unknown issue (programmer error) occurred might lead unpredicted behavior. Differentiating the two allows acting tactfully and applying a balanced approach based on the given context
否则:您可能总是在出现错误时重新启动应用程序,但为什么会因为一个小错误和预测错误(操作错误)而让大约 5000 名在线用户宕机?相反的情况也不理想——在发生未知问题(程序员错误)时保持应用程序运行可能会导致不可预测的行为。区分两者允许根据给定的上下文巧妙地采取行动并应用平衡的方法
Code example - doing it right
代码示例 - 做对了
//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?"));
code example - marking an error as operational (trusted)
代码示例 - 将错误标记为可操作(受信任)
//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);
});
Blog Quote: "Otherwise you risk the state" (From the blog debugable, ranked 3 for the keywords "Node.JS uncaught exception")
博客引用:“否则你会冒险”(来自博客 debugable,关键字“Node.JS 未捕获异常”排名第 3)
"…By the very nature of how throw works in JavaScript, there is almost never any way to safely “pick up where you left off”, without leaking references, or creating some other sort of undefined brittle state. The safest way to respond to a thrown error is to shut down the process. Of course, in a normal web server, you might have many connections open, and it is not reasonable to abruptly shut those down because an error was triggered by someone else. The better approach is to send an error response to the request that triggered the error, while letting the others finish in their normal time, and stop listening for new requests in that worker"
“ ...根据 JavaScript 中 throw 工作方式的本质,几乎没有任何方法可以安全地“从上次中断的地方继续”,而不会泄漏引用或创建其他类型的未定义脆弱状态。最安全的响应方式抛出的错误是关闭进程。当然,在正常的Web服务器中,您可能会打开许多连接,并且由于错误是由其他人触发而突然关闭这些连接是不合理的。更好的方法是向触发错误的请求发送错误响应,同时让其他人在正常时间内完成,并停止侦听该工作人员中的新请求”
Number4: Handle errors centrally, through but not within middleware
Number4:集中处理错误,通过中间件而不是在中间件内
TL;DR:Error handling logic such as mail to admin and logging should be encapsulated in a dedicated and centralized object that all end-points (e.g. Express middleware, cron jobs, unit-testing) call when an error comes in.
TL;DR:错误处理逻辑(例如发送给管理员的邮件和日志记录)应该封装在一个专用的集中式对象中,所有端点(例如 Express 中间件、cron 作业、单元测试)在出现错误时都会调用该对象。
Otherwise:Not handling errors within a single place will lead to code duplication and probably to errors that are handled improperly
否则:不在一个地方处理错误将导致代码重复,并可能导致错误处理不当
Code example - a typical error flow
代码示例 - 一个典型的错误流
//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);
});
});
Blog quote:"Sometimes lower levels can't do anything useful except propagate the error to their caller" (From the blog Joyent, ranked 1 for the keywords “Node.JS error handling”)
博客引用:“有时低层级除了将错误传播给调用者之外,什么也做不了”(来自博客 Joyent,关键字“Node.JS 错误处理”排名第一)
"…You may end up handling the same error at several levels of the stack. This happens when lower levels can't do anything useful except propagate the error to their caller, which propagates the error to its caller, and so on. Often, only the top-level caller knows what the appropriate response is, whether that's to retry the operation, report an error to the user, or something else. But that doesn't mean you should try to report all errors to a single top-level callback, because that callback itself can't know in what context the error occurred"
“……您可能最终会在堆栈的多个级别处理相同的错误。当较低级别无法做任何有用的事情时就会发生这种情况,除了将错误传播给他们的调用者,然后将错误传播给它的调用者,依此类推。通常,只有顶级调用者知道适当的响应是什么,无论是重试操作、向用户报告错误还是其他什么。但这并不意味着您应该尝试将所有错误报告给单个顶级回调,因为回调本身无法知道错误发生在什么上下文中”
Number5: Document API errors using Swagger
编号 5:使用 Swagger 记录 API 错误
TL;DR:Let your API callers know which errors might come in return so they can handle these thoughtfully without crashing. This is usually done with REST API documentation frameworks like Swagger
TL;DR:让您的 API 调用者知道哪些错误可能会返回,以便他们可以周到地处理这些错误而不会崩溃。这通常是通过像 Swagger 这样的 REST API 文档框架来完成的
Otherwise:An API client might decide to crash and restart only because he received back an error he couldn't understand. Note: the caller of your API might be you (very typical in a microservices environment)
否则:API 客户端可能会因为收到一个他无法理解的错误而决定崩溃并重新启动。注意:您的 API 的调用者可能是您(在微服务环境中非常典型)
Blog quote:"You have to tell your callers what errors can happen" (From the blog Joyent, ranked 1 for the keywords “Node.JS logging”)
博客引用:“你必须告诉你的调用者会发生什么错误”(来自博客 Joyent,关键词“Node.JS logging”排名第一)
…We've talked about how to handle errors, but when you're writing a new function, how do you deliver errors to the code that called your function? …If you don't know what errors can happen or don't know what they mean, then your program cannot be correct except by accident. So if you're writing a new function, you have to tell your callers what errors can happen and what they mea
…我们已经讨论了如何处理错误,但是当您编写一个新函数时,您如何将错误传递给调用您的函数的代码?…如果您不知道会发生什么错误或不知道它们的含义,那么您的程序不可能是正确的,除非是偶然的。所以如果你正在编写一个新函数,你必须告诉你的调用者可能会发生什么错误以及它们的含义
Number6: Shut the process gracefully when a stranger comes to town
第 6 条:当陌生人进城时,优雅地关闭流程
TL;DR:When an unknown error occurs (a developer error, see best practice number #3)- there is uncertainty about the application healthiness. A common practice suggests restarting the process carefully using a ‘restarter' tool like Forever and PM2
TL;DR:当发生未知错误时(开发人员错误,请参阅最佳实践编号 #3)- 应用程序健康存在不确定性。一种常见的做法是使用 Forever 和 PM2 等“重启”工具小心地重启进程
Otherwise:When an unfamiliar exception is caught, some object might be in a faulty state (e.g an event emitter which is used globally and not firing events anymore due to some internal failure) and all future requests might fail or behave crazily
否则:当一个不熟悉的异常被捕获时,某些对象可能处于错误状态(例如,全局使用的事件发射器由于某些内部故障而不再触发事件)并且所有未来的请求可能会失败或表现疯狂
Code example - deciding whether to crash
代码示例——决定是否崩溃
//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;
}
Blog quote:"There are three schools of thoughts on error handling" (From the blog jsrecipes)
博客引用:“关于错误处理的三种思想流派”(来自博客 jsrecipes)
…There are primarily three schools of thoughts on error handling: 1. Let the application crash and restart it. 2. Handle all possible errors and never crash. 3. Balanced approach between the two
...关于错误处理主要有三种思想流派: 1. 让应用程序崩溃并重新启动它。2.处理所有可能的错误,永不崩溃。3. 两者兼顾
Number7: Use a mature logger to increase errors visibility
Number7:使用成熟的记录器来增加错误可见性
TL;DR:A set of mature logging tools like Winston, Bunyan or Log4J, will speed-up error discovery and understanding. So forget about console.log.
TL;DR:一套成熟的日志工具,如 Winston、Bunyan 或 Log4J,将加速错误发现和理解。所以忘记console.log。
Otherwise:Skimming through console.logs or manually through messy text file without querying tools or a decent log viewer might keep you busy at work until late
否则:在没有查询工具或体面的日志查看器的情况下浏览 console.logs 或手动浏览凌乱的文本文件可能会让你忙到很晚
Code example - Winston logger in action
代码示例 - 运行中的 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' });
Blog quote:"Lets identify a few requirements (for a logger):" (From the blog strongblog)
博客引用:“让我们确定一些要求(对于记录器):”(来自博客 strongblog)
…Lets identify a few requirements (for a logger): 1. Time stamp each log line. This one is pretty self explanatory – you should be able to tell when each log entry occured. 2. Logging format should be easily digestible by humans as well as machines. 3. Allows for multiple configurable destination streams. For example, you might be writing trace logs to one file but when an error is encountered, write to the same file, then into error file and send an email at the same time…
…让我们确定一些要求(对于记录器): 1. 为每个日志行添加时间戳。这是不言自明的——您应该能够知道每个日志条目何时发生。2. 日志格式应易于人类和机器理解。3. 允许多个可配置的目标流。例如,您可能正在将跟踪日志写入一个文件,但是当遇到错误时,写入同一个文件,然后写入错误文件并同时发送一封电子邮件......
Number8: Discover errors and downtime using APM products
Number8:使用 APM 产品发现错误和停机时间
TL;DR:Monitoring and performance products (a.k.a APM) proactively gauge your codebase or API so they can auto-magically highlight errors, crashes and slow parts that you were missing
TL;DR:监控和性能产品(又名 APM)主动评估您的代码库或 API,以便它们可以自动神奇地突出显示您遗漏的错误、崩溃和缓慢的部分
Otherwise:You might spend great effort on measuring API performance and downtimes, probably you'll never be aware which are your slowest code parts under real world scenario and how these affects the UX
否则:您可能会花费大量精力来衡量 API 性能和停机时间,可能您永远不会意识到在现实世界场景中哪些是最慢的代码部分以及它们如何影响 UX
Blog quote:"APM products segments" (From the blog Yoni Goldberg)
博客引用:“APM 产品细分”(来自博客 Yoni Goldberg)
"…APM products constitutes 3 major segments:1. Website or API monitoring –external services that constantly monitor uptime and performance via HTTP requests. Can be setup in few minutes. Following are few selected contenders: Pingdom, Uptime Robot, and New Relic 2. Code instrumentation –products family which require to embed an agent within the application to benefit feature slow code detection, exceptions statistics, performance monitoring and many more. Following are few selected contenders: New Relic, App Dynamics 3. Operational intelligence dashboard –these line of products are focused on facilitating the ops team with metrics and curated content that helps to easily stay on top of application performance. This is usually involves aggregating multiple sources of information (application logs, DB logs, servers log, etc) and upfront dashboard design work. Following are few selected contenders: Datadog, Splunk"
“……APM 产品由 3 个主要部分构成:1. 网站或 API 监控——通过 HTTP 请求持续监控正常运行时间和性能的外部服务。可以在几分钟内完成设置。以下是几个选定的竞争者:Pingdom、Uptime Robot 和 New Relic 2 . 代码检测 –需要在应用程序中嵌入代理以实现慢速代码检测、异常统计、性能监控等功能的产品系列。以下是少数选定的竞争者:New Relic、App Dynamics 3. 运营智能仪表板 –这些产品系列专注于通过指标和精选内容为运营团队提供便利,帮助轻松掌握应用程序性能。这通常涉及聚合多个信息源(应用程序日志、数据库日志、服务器日志等)和前期仪表板设计工作。以下是几个选定的竞争者:Datadog、Splunk”
The above is a shortened version - see here more best practices and examples
以上是一个缩短的版本 -在这里查看更多最佳实践和示例
回答by nponeccop
You can catch uncaught exceptions, but it's of limited use. See http://debuggable.com/posts/node-js-dealing-with-uncaught-exceptions:4c933d54-1428-443c-928d-4e1ecbdd56cb
您可以捕获未捕获的异常,但它的用途有限。见http://debuggable.com/posts/node-js-dealing-with-uncaught-exceptions:4c933d54-1428-443c-928d-4e1ecbdd56cb
monit, foreveror upstartcan be used to restart node process when it crashes. A graceful shutdown is best you can hope for (e.g. save all in-memory data in uncaught exception handler).
monit,forever或upstart可用于在节点崩溃时重新启动节点进程。正常关闭是您所希望的(例如,将所有内存中的数据保存在未捕获的异常处理程序中)。
回答by B T
nodejs domainsis the most up to date way of handling errors in nodejs. Domains can capture both error/other events as well as traditionally thrown objects. Domains also provide functionality for handling callbacks with an error passed as the first argument via the intercept method.
nodejs 域是处理 nodejs 中错误的最新方法。域可以捕获错误/其他事件以及传统上抛出的对象。域还提供了处理回调的功能,错误是通过拦截方法作为第一个参数传递的。
As with normal try/catch-style error handling, is is usually best to throw errors when they occur, and block out areas where you want to isolate errors from affecting the rest of the code. The way to "block out" these areas are to call domain.run with a function as a block of isolated code.
与正常的 try/catch 风格的错误处理一样,通常最好在错误发生时抛出错误,并屏蔽您想要隔离错误以免影响其余代码的区域。“屏蔽”这些区域的方法是使用作为隔离代码块的函数调用 domain.run。
In synchronous code, the above is enough - when an error happens you either let it be thrown through, or you catch it and handle there, reverting any data you need to revert.
在同步代码中,以上就足够了 - 当发生错误时,您要么让它被抛出,要么捕获它并在那里处理,恢复您需要恢复的任何数据。
try {
//something
} catch(e) {
// handle data reversion
// probably log too
}
When the error happens in an asynchronous callback, you either need to be able to fully handle the rollback of data (shared state, external data like databases, etc). OR you have to set something to indicate that an exception has happened - where ever you care about that flag, you have to wait for the callback to complete.
当错误发生在异步回调中时,您要么需要能够完全处理数据的回滚(共享状态、数据库等外部数据等)。或者你必须设置一些东西来表明发生了异常——无论你关心那个标志,你都必须等待回调完成。
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
}
})});
Some of that above code is ugly, but you can create patterns for yourself to make it prettier, eg:
上面的一些代码很丑,但是您可以为自己创建模式以使其更漂亮,例如:
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
});
UPDATE (2013-09):
更新(2013-09):
Above, I use a future that implies fibers semantics, which allow you to wait on futures in-line. This actually allows you to use traditional try-catch blocks for everything- which I find to be the best way to go. However, you can't always do this (ie in the browser)...
上面,我使用了一个暗示纤维语义的未来,它允许你在线等待期货。这实际上使您可以使用传统的try-catch块的一切-我觉得这是最好的一段路要走。但是,您不能总是这样做(即在浏览器中)...
There are also futures that don't require fibers semantics (which then work with normal, browsery JavaScript). These can be called futures, promises, or deferreds (I'll just refer to futures from here on). Plain-old-JavaScript futures libraries allow errors to be propagated between futures. Only some of these libraries allow any thrown future to be correctly handled, so beware.
还有一些期货不需要纤维语义(然后可以使用普通的浏览器 JavaScript)。这些可以称为期货、承诺或延期(从这里开始我将只提到期货)。普通的 JavaScript 期货库允许在期货之间传播错误。只有其中一些库允许正确处理任何抛出的未来,所以要小心。
An example:
一个例子:
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()
This mimics a normal try-catch, even though the pieces are asynchronous. It would print:
这模仿了正常的 try-catch,即使这些部分是异步的。它会打印:
1
2
handler
Note that it doesn't print '3' because an exception was thrown that interrupts that flow.
请注意,它不会打印“3”,因为引发了中断该流程的异常。
Take a look at bluebird promises:
看看蓝鸟的承诺:
Note that I haven't found many other libraries other than these that properly handle thrown exceptions. jQuery's deferred, for example, don't - the "fail" handler would never get the exception thrown an a 'then' handler, which in my opinion is a deal breaker.
请注意,除了这些可以正确处理抛出的异常的库之外,我还没有找到许多其他库。例如,jQuery 的延迟不会——“失败”处理程序永远不会抛出异常并抛出“然后”处理程序,在我看来,这是一个交易破坏者。
回答by Simon Maynard
I wrote about this recently at http://snmaynard.com/2012/12/21/node-error-handling/. A new feature of node in version 0.8 is domains and allow you to combine all the forms of error handling into one easier manage form. You can read about them in my post.
我最近在http://snmaynard.com/2012/12/21/node-error-handling/ 上写了这篇文章。0.8 版本中 node 的一个新特性是域,它允许您将所有错误处理形式组合成一个更容易管理的形式。您可以在我的帖子中阅读有关它们的信息。
You can also use something like Bugsnagto track your uncaught exceptions and be notified via email, chatroom or have a ticket created for an uncaught exception (I am the co-founder of Bugsnag).
您还可以使用Bugsnag 之类的工具来跟踪未捕获的异常,并通过电子邮件、聊天室收到通知,或者为未捕获的异常创建票证(我是 Bugsnag 的联合创始人)。
回答by Michael Yagudaev
One instance where using a try-catch might be appropriate is when using a forEach loop. It is synchronous but at the same time you cannot just use a return statement in the inner scope. Instead a try and catch approach can be used to return an Error object in the appropriate scope. Consider:
使用 try-catch 可能合适的一种情况是使用 forEach 循环。它是同步的,但同时你不能只在内部范围内使用 return 语句。相反,可以使用 try 和 catch 方法在适当的范围内返回一个 Error 对象。考虑:
function processArray() {
try {
[1, 2, 3].forEach(function() { throw new Error('exception'); });
} catch (e) {
return e;
}
}
It is a combination of the approaches described by @balupton above.
它是上面@balupton 描述的方法的组合。
回答by Michael Yagudaev
I would just like to add that Step.js libraryhelps you handle exceptions by always passing it to the next step function. Therefore you can have as a last step a function that check for any errors in any of the previous steps. This approach can greatly simplify your error handling.
我只想补充一点,Step.js 库通过始终将异常传递给下一步函数来帮助您处理异常。因此,您可以在最后一步使用一个函数来检查前面任何步骤中的任何错误。这种方法可以大大简化您的错误处理。
Below is a quote from the github page:
以下是来自 github 页面的引用:
any exceptions thrown are caught and passed as the first argument to the next function. As long as you don't nest callback functions inline your main functions this prevents there from ever being any uncaught exceptions. This is very important for long running node.JS servers since a single uncaught exception can bring the whole server down.
任何抛出的异常都会被捕获并作为第一个参数传递给下一个函数。只要您不嵌套回调函数内联您的主要函数,这可以防止出现任何未捕获的异常。这对于长时间运行的 node.JS 服务器非常重要,因为一个未捕获的异常可能会导致整个服务器宕机。
Furthermore, you can use Step to control execution of scripts to have a clean up section as the last step. For example if you want to write a build script in Node and report how long it took to write, the last step can do that (rather than trying to dig out the last callback).
此外,您可以使用 Step 来控制脚本的执行,将清理部分作为最后一步。例如,如果你想在 Node 中写一个构建脚本并报告写了多长时间,最后一步可以这样做(而不是试图挖掘出最后一个回调)。
回答by Sudsy
After reading this post some time ago I was wondering if it was safe to use domains for exception handling on an api / function level. I wanted to use them to simplify exception handling code in each async function I wrote. My concern was that using a new domain for each function would introduce significant overhead. My homework seems to indicate that there is minimal overhead and that performance is actually better with domains than with try catch in some situations.
前段时间阅读这篇文章后,我想知道在 api / 函数级别使用域进行异常处理是否安全。我想使用它们来简化我编写的每个异步函数中的异常处理代码。我担心为每个功能使用一个新域会引入大量开销。我的作业似乎表明开销很小,并且在某些情况下域的性能实际上比 try catch 更好。
http://www.lighthouselogic.com/#/using-a-new-domain-for-each-async-function-in-node/
http://www.lighthouselogic.com/#/using-a-new-domain-for-each-async-function-in-node/
回答by K. Craven
Catching errors has been very well discussed here, but it's worth remembering to log the errors out somewhere so you can view them and fix stuff up.
捕获错误在这里已经讨论得很好,但值得记住的是将错误记录在某个地方,以便您可以查看它们并修复它们。
?Bunyan is a popular logging framework for NodeJS - it supporst writing out to a bunch of different output places which makes it useful for local debugging, as long as you avoid console.log. ? In your domain's error handler you could spit the error out to a log file.
?Bunyan 是一个流行的 NodeJS 日志框架——它支持写出到一堆不同的输出位置,这使得它对本地调试很有用,只要你避免使用 console.log。? 在您域的错误处理程序中,您可以将错误输出到日志文件中。
var log = bunyan.createLogger({
name: 'myapp',
streams: [
{
level: 'error',
path: '/var/tmp/myapp-error.log' // log ERROR to this file
}
]
});
This can get time consuming if you have lots of errors and/or servers to check, so it could be worth looking into a tool like Raygun (disclaimer, I work at Raygun) to group errors together - or use them both together. ? If you decided to use Raygun as a tool, it's pretty easy to setup too
如果您有很多错误和/或服务器要检查,这可能会很耗时,因此可能值得研究像 Raygun(免责声明,我在 Raygun 工作)之类的工具将错误组合在一起 - 或同时使用它们。? 如果您决定使用 Raygun 作为工具,它也很容易设置
var raygunClient = new raygun.Client().init({ apiKey: 'your API key' });
raygunClient.send(theError);
? Crossed with using a tool like PM2 or forever, your app should be able to crash, log out what happened and reboot without any major issues.
? 与使用 PM2 之类的工具交叉使用或永远使用,您的应用程序应该能够崩溃,注销发生的事情并重新启动而不会出现任何重大问题。
回答by Cap
If you want use Services in Ubuntu(Upstart): Node as a service in Ubuntu 11.04 with upstart, monit and forever.js
如果你想在 Ubuntu(Upstart) 中使用服务:Node as a service in Ubuntu 11.04 with upstart, monit and ever.js

