ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
# 域 ~~~ 稳定度: 2 - 不稳定 ~~~ Domains 提供了一种方式,即以一个单一的组的形式来处理多个不同的IO操作。如果任何一个注册到domain的事件触发器或回调触发了一个‘error’事件,或者抛出一个错误,那么domain对象将会被通知到。而不是直接让这个错误的上下文从`process.on('uncaughtException')'处理程序中丢失掉,也不会致使程序因为这个错误伴随着错误码立即退出。 ### 警告: 不要忽视错误! Domain error处理程序不是一个在错误发生时,关闭你的进程的替代品 基于'抛出(throw)'在JavaScript中工作的方式,几乎从来没有任何方式能够在‘不泄露引用,不造成一些其他种类的未定义的脆弱状态’的前提下,安全的“从你离开的地方重新拾起(pick up where you left off)”, 响应一个被抛出错误的最安全方式就是关闭进程。当然,在一个正常的Web服务器中,你可能会有很多活跃的连接。由于其他触发的错误你去突然关闭这些连接是不合理。 更好的方法是发送错误响应给那个触发错误的请求,在保证其他人正常完成工作时,停止监听那个触发错误的人的新请求。 在这种方式中,`域`使用伴随着集群模块,由于主过程可以叉新工人时,一个工人发生了一个错误。节点程序规模的多 机,终止代理或服务注册可以注意一下失败,并做出相应的反应。 举例来说,以下就不是一个好想法: var d = require('domain').create(); d.on('error', function(er) { // 这个错误不会导致进程崩溃,但是情况会更糟糕! // 虽然我们阻止了进程突然重启动,但是我们已经发生了资源泄露 // 这种事情的发生会让我们发疯。 // 不如调用 process.on('uncaughtException')! console.log('error, but oh well', er.message); }); d.run(function() { require('http').createServer(function(req, res) { handleRequest(req, res); }).listen(PORT); }); ~~~ <!-- endsection --> <!-- section:c00fe3bdad4d3b86dcb006c3a8c55c76 --> 通过对域的上下文的使用,以及将我们的程序分隔成多个工作进程的反射,我们可以做出更加恰当的反应和更加安全的处理。 <!-- endsection --> <!-- section:1749533625128d664ce57f327a3eb683 --> ```javascript // 好一些的做法! <!-- endsection --> <!-- section:1002b470aa3001d212344187000af555 --> var cluster = require('cluster'); var PORT = +process.env.PORT || 1337; <!-- endsection --> <!-- section:2f82bd01f23b2a29fbda6b4d88878ba5 --> if (cluster.isMaster) { // 在工作环境中,你可能会使用到不止一个工作分支 // 而且可能不会把主干和分支放在同一个文件中 // //你当然可以通过日志进行猜测,并且对你需要防止的DoS攻击等不良行为实施自定义的逻辑 // // 看集群文件的选项 // // 最重要的是主干非常小,增加了我们抵抗以外错误的可能性。 <!-- endsection --> <!-- section:cb647d887934b4f503df68aec3e5bb82 --> cluster.fork(); cluster.fork(); <!-- endsection --> <!-- section:73e4dad498a584605744c08e8889acd2 --> cluster.on('disconnect', function(worker) { console.error('disconnect!'); cluster.fork(); }); <!-- endsection --> <!-- section:1eb9320833f0f4735c2a02437b831763 --> } else { // 工作进程 // // 这是我们出错的地方 <!-- endsection --> <!-- section:4e5957024feac866ed9559ec321d9348 --> var domain = require('domain'); <!-- endsection --> <!-- section:3eb2409b6e7d143f8eea5a1e547278b1 --> //看集群文件对于使用工作进程处理请求的更多细节,它是如何工作的,它的警告等等。 <!-- endsection --> <!-- section:dd3bdd23463afb9f443cd297ec716fb5 --> var server = require('http').createServer(function(req, res) { var d = domain.create(); d.on('error', function(er) { console.error('error', er.stack); <!-- endsection --> <!-- section:e614fa9e1583991a0d9b352d554182ba --> // 因为req和res在这个域存在之前就被创建, // 所以我们需要显式添加它们。 // 详见下面关于显式和隐式绑定的解释。 d.add(req); d.add(res); <!-- endsection --> <!-- section:ec427556232abea87deeffa7ae5a5830 --> // 现在在域里面运行处理器函数。 d.run(function() { handleRequest(req, res); }); }); server.listen(PORT); } <!-- endsection --> <!-- section:8792f2c8a87d64cd7a3893d4f03b9c89 --> // 这个部分不是很重要。只是一个简单的路由例子。 // 你会想把你的超级给力的应用逻辑放在这里。 function handleRequest(req, res) { switch(req.url) { case '/error': // 我们干了一些异步的东西,然后。。。 setTimeout(function() { // 呃。。。 flerb.bark(); }); break; default: res.end('ok'); } } ~~~ ### 对Error(错误)对象的内容添加 每一次一个Error对象被导向经过一个域,它会添加几个新的字段。 - `error.domain` 第一个处理这个错误的域。 - `error.domainEmitter` 用这个错误对象触发'error'事件的事件分发器。 - `error.domainBound` 回调函数,该回调函数被绑定到域,并且一个错误会作为第一参数传递给这个回调函数。 - `error.domainThrown` 一个布尔值表明这个错误是否被抛出,分发或者传递给一个绑定的回调函数。 ### 隐式绑定 如果多个域正在被使用,那么所有的**新**EventEmitter对象(包括Stream对象,请求,应答等等)会被隐式绑定到它们被创建时的有效域。 而且,被传递到低层事件分发请求的回调函数(例如fs.open,或者其它接受回调函数的函数)会自动绑定到有效域。如果这些回调函数抛出错误,那么这个域会捕捉到这个错误。 为了防止内存的过度使用,Domain对象自己不会作为有效域的子对象被隐式添加到有效域。因为如果这样做的话,会很容易影响到请求和应答对象的正常垃圾回收。 如果你*想*在一个父Domain对象里嵌套子Domain对象,那么你需要显式地添加它们。 隐式绑定将被抛出的错误和`'error'`事件导向到Domain对象的`error`事件,但不会注册到Domain对象上的EventEmitter对象,所以`domain.dispose()`不会令EventEmitter对象停止运作。隐式绑定只关心被抛出的错误和 `'error'`事件。 ### 显式绑定 有时,正在使用的域并不是某个事件分发器所应属的域。又或者,事件分发器在一个域内被创建,但是应该被绑定到另一个域。 例如,对于一个HTTP服务器,可以有一个正在使用的域,但我们可能希望对每一个请求使用一个不同的域。 这可以通过显示绑定来达到。 例如: ~~~ serverDomain.run(function() { // 服务器在serverDomain的作用域内被创建 http.createServer(function(req, res) { // req和res同样在serverDomain的作用域内被创建 // 但是,我们想对于每一个请求使用一个不一样的域。 // 所以我们首先创建一个域,然后将req和res添加到这个域上。 var reqd = domain.create(); reqd.add(req); reqd.add(res); reqd.on('error', function(er) { console.error('Error', er, req.url); try { res.writeHead(500); res.end('Error occurred, sorry.'); } catch (er) { console.error('Error sending 500', er, req.url); } }); }).listen(1337); }); ``` ~~~ ### domain.create() - return: {Domain} 返回一个新的Domain对象。 ### 类: Domain Domain类封装了将错误和没有被捕捉的异常导向到有效对象的功能。 Domain是 [EventEmitter](#)类的一个子类。监听它的`error`事件来处理它捕捉到的错误。 ### domain.run(fn) - `fn` {Function} 在域的上下文里运行提供的函数,隐式地绑定所有该上下文里创建的事件分发器,计时器和低层请求。 这是使用一个域的最基本的方式。 实例: ~~~ var d = domain.create(); d.on('error', function(er) { console.error('Caught error!', er); }); d.run(function() { process.nextTick(function() { setTimeout(function() { // 模拟几个不同的异步的东西 fs.open('non-existent file', 'r', function(er, fd) { if (er) throw er; // 继续。。。 }); }, 100); }); }); ~~~ 在这个例子里, `d.on('error')` 处理器会被触发,而不是导致程序崩溃。 ### domain.members - {Array} 一个数组,里面的元素是被显式添加到域里的计时器和事件分发器。 ### domain.add(emitter) - `emitter` {EventEmitter | Timer} 被添加到域里的时间分发器或计时器 显式地将一个分发器添加到域。如果这个分发器调用的任意一个事件处理器抛出一个错误,或是这个分发器分发了一个`error`事,那么它会被导向到这个域的`error`事件,就像隐式绑定所做的一样。 这对于从`setInterval`和`setTimeout`返回的计时器同样适用。如果这些计时器的回调函数抛出错误,它将会被这个域的`error`处理器捕捉到。 如果这个Timer或EventEmitter对象已经被绑定到另外一个域,那么它将会从那个域被移除,然后绑定到当前的域。 ### domain.remove(emitter) - `emitter` {EventEmitter | Timer} 要从域里被移除的分发器或计时器 与`domain.add(emitter)`函数恰恰相反,这个函数将域处理从指明的分发器里移除。 ### domain.bind(callback) - `callback` {Function} 回调函数 - return: {Function} 被绑定的函数 返回的函数会是一个对于所提供的回调函数的包装函数。当这个被返回的函数被调用时,所有被抛出的错误都会被导向到这个域的`error`事件。 #### 例子 ~~~ d.on('error', function(er) { // 有个地方发生了一个错误。 // 如果我们现在抛出这个错误,它会让整个程序崩溃 // 并给出行号和栈信息。 }); ~~~ ### domain.intercept(callback) - `callback` {Function} 回调函数 - return: {Function} 被拦截的函数 这个函数与`domain.bind(callback)`几乎一模一样。但是,除了捕捉被抛出的错误外,它还会拦截作为第一参数被传递到这个函数的`Error`对象。 在这种方式下,常见的'if(er) return callback(er);'的方式可以被一个单独地方的单独的错误处理所取代。 #### 例子 ~~~ d.on('error', function(er) { // 有个地方发生了一个错误。 // 如果我们现在抛出这个错误,它会让整个程序崩溃 // 并给出行号和栈信息。 }); ~~~ ### domain.enter() `enter`函数对于`run`,`bind`和`intercept`来说就像它们的管道系统:它们使用`enter`函数来设置有效域。`enter`函数对于域设定了`domain.active`和 `process.domain` ,还隐式地将域推入了由域模块管理的域栈(关于域栈的细节详见`domain.exit()`)。`enter`函数的调用,分隔了异步调用链以及绑定到一个域的I/O操作的结束或中断。 调用`enter`仅仅改变活动的域,而不改变域本身。 `Enter` 和 `exit`在一个单独的域可以被调用任意多次。 如果域的`enter`已经设置,`enter`将不设置域就返回。 ### domain.exit() `exit`函数退出当前的域,将当前域从域的栈里移除。每当当程序的执行流程准要切换到一个不同的异步调用链的上下文时,要保证退出当前的域。`exit`函数的调用,分隔了异步调用链以及绑定到一个域的I/O操作的结束或中断。 如果有多个嵌套的域绑定到当前的执行上下文, `退出`将退出在这个域里的所有的嵌套。 调用`exit`只会改变有效域,而不会改变域自身。在一个单一域上,`Enter`和`exit`可以被调用任意次。 如果在这个域名下`exit` 已经被设置,`exit` 将不退出域返回。 ### domain.dispose() ~~~ 稳定度: 0 - 已过时。请通过设置在域上的错误事件处理器,显式地东失败的IO操作中恢复。 ~~~ 一旦`dispose`被调用,通过`run`,`bind`或`intercept`绑定到这个域的回调函数将不再使用这个域,并且一个`dispose`事件会被分发。