# 域
~~~
穩定度: 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`事件會被分發。