了解Try / Catch和Domains在Node中的error handling

我一直在研究正确的方法来处理Node中的错误,并且在StackOverflow和NodeJS的站点上find了一些很好的答案,例如如何防止node.js崩溃? try-catch不起作用 ,并且NodeJS自己logging: http : //nodejs.org/api/domain.html 。

不过,我还剩下几个问题,关于何时何地使用try / catch和/或域名。 我意识到这与asynchronous代码和同步代码有关,但即使在NodeJS网站提供的有关域的示例代码中,它们也会在域的error handling程序中使用try / catch。 有人可以解释一些细节,try / catch不会在error handling程序中捕获asynchronous错误吗?

除此之外,NodeJS的文档build议您仍然应该结束exception进程,这就是为什么来自Domain文档的代码build议在发生exception时使用集群来分派新的subprocess/ worker。 主要的原因是:

由于JavaScript在JavaScript中的工作原理,几乎没有任何方法可以安全地“捡起你离开的地方”,而不会泄漏引用或创build其他某种不明确的状态。

有人可以解释这个吗? 抛出在JavaScript中如何工作的本质是什么? 为什么资源会疯狂泄漏? 是否真的有必要重新启动进程或杀死/启动一个工人?

例如,我正在实现JugglingDB ORM,并且曾经忘记启动我的本地mysql服务器。 我遇到了一个ECONNREFUSED错误,导致程序崩溃。 意识到这可能发生在生产环境(数据库崩溃或暂时不可用),我想抓住这个错误,并妥善处理; 重试连接,维护关于数据库的状态variables,并可能通过响应暂时不可用的消息来处理请求。 Try / Catch根本没有发现错误,尽pipe我看到我可以使用一个域名,但是使用推荐的策略,我将处于无休止的杀死和启动工作的循环,直到数据库恢复在线。

JugglingDB ,无论出于何种原因,只有一个“连接”的事件,但没有任何forms的callback函数,通过一个错误的对象; 它试图连接你实例化类的那一刻,并抛出没有捕获并以优美的方式散发的错误。 这使我想看看其他的ORM,但这仍然不能回答我有关如何处理这种情况的问题。 使用一个域来捕获潜在的连接错误,并在没有启动一个新进程的情况下正常处理它会是错误的吗?

这里是我发布到JugglingDB github上的问题: https : //github.com/1602/jugglingdb/issues/405 ,这里是JugglingDB在服务器不存在时产生的错误的堆栈跟踪在启用池选项时发生 ):

 Error: connect ECONNREFUSED at errnoException (net.js:901:11) at Object.afterConnect [as oncomplete] (net.js:892:19) -------------------- at Protocol._enqueue (/Users/aaronstorck/Sites/site/node_modules/jugglingdb-mysql/node_modules/mysql/lib/protocol/Protocol.js:110:48) at Protocol.handshake (/Users/aaronstorck/Sites/site/node_modules/jugglingdb-mysql/node_modules/mysql/lib/protocol/Protocol.js:42:41) at PoolConnection.Connection.connect (/Users/aaronstorck/Sites/site/node_modules/jugglingdb-mysql/node_modules/mysql/lib/Connection.js:101:18) at Pool.getConnection (/Users/aaronstorck/Sites/site/node_modules/jugglingdb-mysql/node_modules/mysql/lib/Pool.js:42:23) at Pool.query (/Users/aaronstorck/Sites/site/node_modules/jugglingdb-mysql/node_modules/mysql/lib/Pool.js:185:8) at initDatabase (/Users/aaronstorck/Sites/site/node_modules/jugglingdb-mysql/lib/mysql.js:62:20) at initializeConnection (/Users/aaronstorck/Sites/site/node_modules/jugglingdb-mysql/lib/mysql.js:49:9) at Object.initializeSchema [as initialize] (/Users/aaronstorck/Sites/site/node_modules/jugglingdb-mysql/lib/mysql.js:33:5) at new Schema (/Users/aaronstorck/Sites/site/node_modules/jugglingdb/lib/schema.js:105:13) at Application.loadConnections (/Users/aaronstorck/Sites/site/core/application.js:95:40) Process finished with exit code 8 

提前感谢您的任何部分,您可以帮助我理解! 🙂

看JugglingDB,并试图连接到我的笔记本电脑上不存在的MySQL服务器,我得到了ECONNREFUSED ,但它只是每隔几秒(尝试重新连接)logging,并没有崩溃我的过程。 我将我的jugglingdb-mysql降级到0.0.6,然后我可以复制你说的话。 我看着jugglingdb-mysql源代码,发现如果发现连接上的错误,它只是抛出。 这很糟糕,不是一个好的行为。 虽然垃圾邮件日志也不好,但抛出可以处理的错误更糟。 所以我build议升级到0.0.7或0.0.8(最新),所以你不必担心这个错误。 我只是为JugglingDB或jugglingdb-mysql做一个错误报告,像node-mysql一样正确地传播错误。 node-mysql是如何完成的很好的例子。

现在让我们来看看如何处理node.js中的错误。 不是所有的错误都抛出node.js. 例如,你可以这样做:

 require('fs').readFile('non-existent-file-yes-gimmie-a-error', function (error) { }) 

callback将会被调用,但不会被抛出。 如果你是模块开发人员,那么在这种情况下,总是传播错误。 要么调用带有错误的callback,要么调用error handling程序,或者在事件发生器上发出error事件。 旧版本的jugglingdb-mysql是错误应该如何处理的一个非常糟糕的例子。 如果你不能抓住错误,那么你不应该把它扔掉。 只有当它被捕获时才抛出,就像node.js的核心库函数一样。 如果你require('fs').readFile()它会立即抛出错误,这是可捕获的。 但是在发现错误的情况下,在函数返回(处理asynchronous事件)之后,它会调用带有错误的callback函数。

现在我确定你遇到了更多的崩溃,形成不可捕捉的抛出错误。 他们很可能来自事件发生器error事件。 在发生error事件的node.js中, 如果没有处理程序,则会抛出 。 所以如果你想从事件发射器捕获错误只是添加一个error事件。 这个例子是fs.createReadStream ,它将返回一个事件发射器:

 require('fs').createReadStream('non-exitent-file-gimmie-a-error') 

这肯定会导致进程崩溃,但是如果你能处理这个错误(例如给404,导致这个error的http请求),那么添加一个error处理程序,它不会再抛出错误:

 require('fs').createReadStream('non-exitent-file-gimmie-a-error').on('error', handleError) 

除了I / O错误之外,还有types和引用错误。 那些你必须小心处理它们。

例如,你可以有这样的事情(你不会只是为了教育目的):

 var routes = { '/' : function () {...}, '/one' : function () {...}, '/two' : function () {...} } require('http').createServer(function (req, res) { fs.open('layout.html', 'r', function (err, fd) { if (err) return errorHandler(err); var buffer = new Buffer(4000); // Lets say we know our file is 4000 bytes exatly fs.read(fd, buffer, 0, 4000, function (err, data) { if (err) return errorHandler(err); try { routes[req.url](req, res, data); fs.close(fd); } catch (e) { errorHandler(err); } }); }); function errorHandler(err) { res.writeHead(404); res.end(); } }).listen(1337) 

现在,当你看到如果routes[req.url]不存在,就会抛出一个错误,因为我们试图调用errorHandler,但是这个文件仍然是打开的,我们忘记closures错误。 如果有10000个错误的URL请求,那么你用完了你的进程最大打开文件的限制。 你可以解决这个问题,而是把fs.close(fd)放在finally子句中。

让我们想象一下,但没有trycatch ,但捕捉与全球域的错误。 在某些情况下,你不会知道你的程序的状态,所以错误你不能只是决定让应用程序继续,因为在这种情况下,它会泄漏文件描述符。

这个问题适用于任何我们必须编写清理代码的地方,作为开发人员,我们并不总是考虑所有我们收到的不同input,而且我们总会犯错误。 这就是为什么它build议崩溃的过程。 你可以用process.on('uncaughtException')domain.on('error')process.on('uncaughtException') domain.on('error')但是你必须在完成清理之后domain.on('error')进程。

但是你必须小心这个。 尝试崩溃,当你不知道错误来自何处。 如果你知道它来自哪里(比如上面的例子,我们只打开一个文件),然后清理它的资源,让你的进程继续下去,因为攻击者可以通过恶意inputDOS呢。

在你决定做某些事情然后崩溃的情况下,确保你设置了一个超时,然后在发生超时时崩溃这个进程。 unref节点v0.10上的超时,以确保它不会使进程保持活动状态。在v0.8上,您可以使用addTimeout这样的模块,在调用callback时清除超时。