【问题标题】:Understanding Try/Catch and Domains for Error Handling in Node了解节点中错误处理的 Try/Catch 和域
【发布时间】:2014-07-14 02:10:24
【问题描述】:

我一直在研究在 Node 中处理错误的正确方法,并在 StackOverflow 和 NodeJS 的网站上找到了一些很好的答案,例如 How do I prevent node.js from crashing? try-catch doesn't work 和 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

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

【问题讨论】:

标签: javascript mysql node.js error-handling jugglingdb


【解决方案1】:

查看 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 事件时,if there are no handlers it will thrown。因此,如果您想从事件发射器中捕获错误,只需添加一个 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 子句中。

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

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

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

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

【讨论】:

  • 我试图通过直接从他们的 git 存储库中提取来确保我拥有最新版本的 JugglindDB 和 jugglingdb-mysql。在这种情况下,我认为通常 NPM 位于存储库后面的假设似乎是错误的。奇怪的是,jugglingdb-mysql 的 NPM 为 0.0.8,而 git repo 为 0.0.6,同时它们的发布分支只上升到 0.0.7。我切换到 NPM 版本,现在 jugglingdb 的版本为 0.2.14,jugglingdb-mysql 的版本为 0.0.8。我通过它们各自的 node_modules 文件夹中的 package.json 文件确认了这一点。我的过程仍然以“退出代码 8”结束:/
  • 我需要回复你答案的其他部分,但一次只有一件事:)
  • 只有在开启池化选项时才会出现崩溃错误。但是,您使用 0.0.8 报告的行为(记录错误和自动重新连接尝试)在关闭池的情况下工作。
  • 这与 0.0.6 中发生的行为相同。无条件抛出错误。抓住这一点的唯一方法是使用域。
  • 关于域;它是否允许您仅处理运行上下文中代码部分的错误?或者它是否处理任何未捕获的异常?我是否认为 JugglingDB 糟糕的设计是使用域是捕获错误的唯一方法的原因?您认为尝试寻找替代的 ORM 会更明智,还是尝试分叉和解决问题并提交拉取请求会是微不足道的(也许是一次很好的学习体验)?使用域会不会是一个糟糕的设计选择,就像一个创可贴来覆盖一个糟糕的依赖设计?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-17
  • 1970-01-01
  • 2023-03-03
  • 2022-11-20
  • 2014-01-01
  • 1970-01-01
相关资源
最近更新 更多