了解尝试/捕获和域以便在节点中进行错误处理

时间:2014-05-25 00:17:07

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

我一直在研究处理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文档的代码建议在捕获异常时使用集群来分叉新的子进程/ worker。给出的主要原因是:

  

就如何在JavaScript中使用throw的本质而言,几乎就是这样   从来没有任何方法安全地“拿起你离开的地方”,没有泄漏   引用,或创建一些其他类型的未定义的脆弱状态。

有人可以解释一下吗? throw如何在Javascript中运行的本质是什么?为什么资源会像疯了一样泄漏?是否真的有必要重新启动进程或杀死/启动工作人员?

例如,我正在实现JugglingDB ORM,并且一度忘记启动我的本地mysql服务器。我遇到了ECONNREFUSED错误,导致进程崩溃。意识到这可能发生在生产环境中(数据库崩溃或暂时不可用),我想抓住这个错误并优雅地处理它;重试连接,维护有关数据库的状态变量,并可能通过响应暂时不可用的消息来处理请求。尝试/ 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

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

1 个答案:

答案 0 :(得分:2)

查看JugglingDB,并尝试连接到我的笔记本电脑上不存在的mysql服务器,我得到ECONNREFUSED,但它只是每隔几秒就会记录一次(尝试重新连接)并且不会导致我的进程崩溃。我将jugglingdb-mysql降级到0.0.6,然后我可以复制你说的内容。我查看了jugglingdb-mysql源码,发现连接上的错误是否被抛出。这很糟糕,而且不是一个好的行为。虽然垃圾邮件日志也很糟糕,但抛出可以处理的错误更糟糕。所以我建议升级到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) { })

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

现在我确定你遇到了更多的崩溃,形成无法捕获的抛出错误。它们很可能来自事件发射器error事件。在发出error事件的node.js中,if there are no handlers it will thrown。因此,如果要捕获事件发射器中的错误,只需添加error事件即可。一个例子是fs.createReadStream,它将返回一个事件发射器:

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

这肯定会使进程崩溃,但是如果你可以处理错误(例如给出404,导致这个问题的http请求),那么添加一个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')来捕获错误,但是在完成清理后必须终止该过程。

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

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