2

我一直在研究处理 Node 错误的正确方法,并在 StackOverflow 和 NodeJS 的网站上找到了一些很好的答案,例如如何防止 node.js 崩溃?try-catch 不起作用,NodeJS 文档本身:http://nodejs.org/api/domain.html

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

除此之外,NodeJS 的文档建议您仍然应该在出现异常时结束进程,这就是为什么 Domain 文档中的代码建议在捕获到异常时使用集群来分叉一个新的子进程/工作者。给出的主要原因是:

由于 throw 在 JavaScript 中的工作原理,几乎没有任何方法可以安全地“从上次中断的地方继续”,而不会泄漏引用或创建其他类型的未定义的脆弱状态。

有人可以解释一下吗?throw 在 Javascript 中的工作原理是什么?为什么资源会疯狂泄漏?是否真的有必要重新启动进程或杀死/启动工作人员?

例如,我正在实现JugglingDB ORM,并且有一次忘记启动我的本地 mysql 服务器。我遇到了一个ECONNREFUSED导致进程崩溃的错误。意识到这可能发生在生产环境中(数据库崩溃或暂时不可用),我想捕捉这个错误并优雅地处理它;重试连接,维护有关数据库的状态变量,并可能通过响应暂时不可用的消息来处理请求。Try/Catch 根本没有捕捉到错误,虽然我看到我可以使用域,但使用推荐的策略,我将处于杀死和启动工作人员的无限循环中,直到数据库重新联机。

JugglingDB,无论出于何种原因,只有一个“连接”事件,但没有任何类型的回调函数来传递错误对象;它会在您实例化类的那一刻尝试连接,并抛出错误,这些错误不会以优雅的方式捕获和发出。这让我想看看其他 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

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

4

1 回答 1

2

查看 JugglingDB,并尝试连接到我的笔记本电脑上不存在的 mysql 服务器,我确实得到ECONNREFUSED了,但它每隔几秒钟才记录一次(尝试重新连接)并且不会使我的进程崩溃。我将我的 jugglingdb-mysql 降级到 0.0.6,然后我可以复制你所说的。我查看了 jugglingdb-mysql 源代码,发现如果发现连接错误,它就会被抛出。这是不好的,也不是好的行为。虽然垃圾邮件日志也很糟糕,但抛出可以处理的错误更糟糕。所以我建议升级到 0.0.7 或者 0.0.8(latest),这样你就不用担心这个错误了。我只是为 JugglingDB 或 jugglingdb-mysql 做一个错误报告,以便像 node-mysql 一样正确地传播错误。node-mysql 是一个很好的例子,说明它是如何完成的。

现在让我们看看如何处理 node.js 中的错误。并非所有错误都会在 node.js 中引发。例如,您可以这样做:

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

回调将被调用并出现错误,但不会被抛出。如果您是模块开发人员,那么在这种情况下,请始终传播错误。调用带有错误的回调,或调用错误处理程序或error在事件发射器上发出事件。旧版本的 jugglingdb-mysql 是一个非常糟糕的错误处理示例。如果你不能用 捕获错误try catch,那么你不应该抛出它。只有在可以被捕获的时候才抛出,就像 node.js 核心库函数一样。如果你这样做require('fs').readFile(),它会立即抛出错误,这是可以捕获的。但是在发现错误的情况下,在函数返回后(处理异步事件),它将调用带有错误的回调。

现在我敢肯定你经历过更多的崩溃,形成无法捕获的抛出错误。它们很可能来自事件发射器error事件。在 node.js 中,当一个error事件发出时,如果没有处理程序,它将抛出. 因此,如果您想从事件发射器中捕获错误,只需添加一个error事件。一个例子是fs.createReadStream返回一个事件发射器:

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

这肯定会使进程崩溃,但是如果您可以处理错误(例如,给导致此错误的 http 请求提供 404)然后添加一个error处理程序,它就不会再抛出错误了:

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

除了 I/O 错误,还有类型和引用错误。这些是你必须小心处理它们的那些。

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

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,但是文件保持打开状态,我们忘记在出错时关闭它。如果发出了 10000 个 URL 错误的请求,那么您的进程最大打开文件限制已用完。您可以解决此问题,但将其fs.close(fd)放在 finally 子句中。

让我们想象一下,但没有tryand catch,而是使用全局域捕获错误。在某些情况下,您将不再知道程序的状态,因此在出现错误时您不能只决定让应用程序继续运行,因为在这种情况下,它会泄漏文件描述符。

这个问题适用于我们必须编写清理代码的任何地方,作为开发人员,我们并不总是考虑我们收到的所有不同输入,我们总是会犯错误。这就是为什么建议使进程崩溃的原因。process.on('uncaughtException')您可以使用or捕获错误,domain.on('error')但您必须在完成清理后终止该进程。

但是你必须小心这一点。当您不知道错误来自何处时,请尝试崩溃。如果你确实知道它来自哪里(就像上面的例子,我们只打开了一个文件),然后清理它的资源并让你的进程继续,因为攻击者可以找出如何使用恶意输入来崩溃你的进程和做它。

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

于 2014-05-27T13:22:36.307 回答