穩(wěn)定性: 2 - 不穩(wěn)定
Node.js域包含了能把不同的IO操作看成單獨組的方法。如果任何一個注冊到域的事件或者回調觸發(fā)error
事件,或者拋出一個異常,則域就會接收到通知,而不是在process.on('uncaughtException')
處理程序中丟失錯誤的上下文,也不會使程序立即以錯誤代碼退出。
你不能將域錯誤處理程序看做錯誤發(fā)生時就關閉進程的一個替代方案。
根據(jù)JavaScript中拋出異常的工作原理,基本上沒有方法可以安全的“回到原先離開的位置”,在不泄露引用,或者不造成一些其他未定義的狀態(tài)下。
響應拋出錯誤最安全的方法就是關閉進程。一個正常的服務器會可能有很多活躍的連接,因為某個錯誤就關閉所有連接顯然是不合理的。
比較好的方法是給觸發(fā)錯誤的請求發(fā)送錯誤響應,讓其他連接正常工作時,停止監(jiān)聽觸發(fā)錯誤的人的新請求。
按這種方法,域
和集群(cluster)模塊可以協(xié)同工作,當某個進程遇到錯誤時,主進程可以復制一個新的進程。對于Node程序,終端代理或者注冊的服務,可以留意錯誤并做出反應。
舉例來說,下面的代碼就不是好辦法:
javascript
// XXX WARNING! BAD IDEA!
var d = require('domain').create();
d.on('error', function(er) {
// The error won't crash the process, but what it does is worse!
// Though we've prevented abrupt process restarting, we are leaking
// resources like crazy if this ever happens.
// This is no better than 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);
});
通過使用域的上下文,并將程序切為多個工作進程,我們能夠更合理的響應,處理錯誤更安全:
javascript
// 好一些的做法!
var cluster = require('cluster');
var PORT = +process.env.PORT || 1337;
if (cluster.isMaster) {
// In real life, you'd probably use more than just 2 workers,
// and perhaps not put the master and worker in the same file.
//
// You can also of course get a bit fancier about logging, and
// implement whatever custom logic you need to prevent DoS
// attacks and other bad behavior.
//
// See the options in the cluster documentation.
//
// The important thing is that the master does very little,
// increasing our resilience to unexpected errors.
cluster.fork();
cluster.fork();
cluster.on('disconnect', function(worker) {
console.error('disconnect!');
cluster.fork();
});
} else {
// the worker
//
// This is where we put our bugs!
var domain = require('domain');
// See the cluster documentation for more details about using
// worker processes to serve requests. How it works, caveats, etc.
var server = require('http').createServer(function(req, res) {
var d = domain.create();
d.on('error', function(er) {
console.error('error', er.stack);
// Note: we're in dangerous territory!
// By definition, something unexpected occurred,
// which we probably didn't want.
// Anything can happen now! Be very careful!
try {
// make sure we close down within 30 seconds
var killtimer = setTimeout(function() {
process.exit(1);
}, 30000);
// But don't keep the process open just for that!
killtimer.unref();
// stop taking new requests.
server.close();
// Let the master know we're dead. This will trigger a
// 'disconnect' in the cluster master, and then it will fork
// a new worker.
cluster.worker.disconnect();
// try to send an error to the request that triggered the problem
res.statusCode = 500;
res.setHeader('content-type', 'text/plain');
res.end('Oops, there was a problem!\n');
} catch (er2) {
// oh well, not much we can do at this point.
console.error('Error sending 500!', er2.stack);
}
});
// Because req and res were created before this domain existed,
// we need to explicitly add them.
// See the explanation of implicit vs explicit binding below.
d.add(req);
d.add(res);
// Now run the handler function in the domain.
d.run(function() {
handleRequest(req, res);
});
});
server.listen(PORT);
}
// This part isn't important. Just an example routing thing.
// You'd put your fancy application logic here.
function handleRequest(req, res) {
switch(req.url) {
case '/error':
// We do some async stuff, and then...
setTimeout(function() {
// Whoops!
flerb.bark();
});
break;
default:
res.end('ok');
}
}
任何時候一個錯誤被路由傳到一個域的時,會添加幾個字段。
error.domain
第一個處理錯誤的域error.domainEmitter
用這個錯誤對象觸發(fā)'error'事件的事件分發(fā)器error.domainBound
綁定到domain的回調函數(shù),第一個參數(shù)是error。error.domainThrown
boolean值,表明是拋出錯誤,分發(fā),或者傳遞給綁定的回到函數(shù)。 新分發(fā)的對象(包括流對象(Stream objects),請求(requests),響應(responses)等)會隱式的綁定到當前正在使用的域中。
另外,傳遞給底層事件循環(huán)(比如fs.open或其他接收回調的方法)的回調函數(shù)將會自動的綁定到這個域。如果他們拋出異常,域會捕捉到錯誤信息。
為了避免過度使用內存,域對象不會象隱式的添加為有效域的子對象。如果這樣做的話,很容易影響到請求和響應對象的垃圾回收。
如果你想將域對象作為子對象嵌入到父域里,就必須顯式的添加它們。
隱式綁定路由拋出的錯誤和'error'
事件,但是不會注冊事件分發(fā)器到域,所以domain.dispose()
不會關閉事件分發(fā)器。隱式綁定僅需注意拋出的錯誤和 'error'
事件。
有時候正在使用的域并不是某個事件分發(fā)器的域?;蛘哒f,事件分發(fā)器可能在某個域里創(chuàng)建,但是被綁定到另外一個域里。
例如,HTTP服務器使用正一個域對象,但我們希望可以每一個請求使用一個不同的域。
這可以通過顯式綁定來實現(xiàn)。
例如:
// create a top-level domain for the server
var serverDomain = domain.create();
serverDomain.run(function() {
// server is created in the scope of serverDomain
http.createServer(function(req, res) {
// req and res are also created in the scope of serverDomain
// however, we'd prefer to have a separate domain for each request.
// create it first thing, and add req and res to it.
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);
});
用于返回一個新的域對象。
這個類封裝了將錯誤和沒有捕捉到的異常到有效對象功能。
域是EventEmitter的子類. 監(jiān)聽它的error
事件來處理捕捉到的錯誤。
fn
{Function}在域的上下文運行提供的函數(shù),隱式的綁定了所有的事件分發(fā)器,計時器和底層請求。
這是使用域的基本方法。
例如:
var d = domain.create();
d.on('error', function(er) {
console.error('Caught error!', er);
});
d.run(function() {
process.nextTick(function() {
setTimeout(function() { // simulating some various async stuff
fs.open('non-existent file', 'r', function(er, fd) {
if (er) throw er;
// proceed...
});
}, 100);
});
});
這個例子里程序不會崩潰,而會觸發(fā)d.on('error')
。
顯式添加到域里的計時器和事件分發(fā)器數(shù)組。
emitter
{EventEmitter | Timer} 添加到域里的計時器和事件分發(fā)器顯式地將一個分發(fā)器添加到域。如果分發(fā)器調用的事件處理函數(shù)拋出錯誤,或者分發(fā)器遇到error
事件,將會導向域的error
事件,和隱式綁定一樣。
對于setInterval
和setTimeout
返回的計時器同樣適用。如果這些回調函數(shù)拋出錯誤,將會被域的'error'處理器捕捉到。
如果計時器或分發(fā)器已經(jīng)綁定到域,那它將會從上一個域移除,綁定到當前域。
emitter
{EventEmitter | Timer} 要移除的分發(fā)器或計時器與domain.add(emitter)函數(shù)恰恰相反,這個函數(shù)將分發(fā)器移除出域。
callback
{Function} 回調函數(shù)返回的函數(shù)是一個對于所提供的回調函數(shù)的包裝函數(shù)。當調用這個返回的函數(shù)被時,所有被拋出的錯誤都會被導向到這個域的error
事件。
var d = domain.create();
function readSomeFile(filename, cb) {
fs.readFile(filename, 'utf8', d.bind(function(er, data) {
// if this throws, it will also be passed to the domain
return cb(er, data ? JSON.parse(data) : null);
}));
}
d.on('error', function(er) {
// an error occurred somewhere.
// if we throw it now, it will crash the program
// with the normal line number and stack message.
});
callback
{Function} 回調函數(shù)和domain.bind(callback)
類似。除了捕捉被拋出的錯誤外,它還會攔截Error對象作為參數(shù)傳遞到這個函數(shù)。
這種方式下,常見的if (er) return callback(er);
模式,能被一個地方一個錯誤處理替換。
var d = domain.create();
function readSomeFile(filename, cb) {
fs.readFile(filename, 'utf8', d.intercept(function(data) {
// note, the first argument is never passed to the
// callback since it is assumed to be the 'Error' argument
// and thus intercepted by the domain.
// if this throws, it will also be passed to the domain
// so the error-handling logic can be moved to the 'error'
// event on the domain instead of being repeated throughout
// the program.
return cb(null, JSON.parse(data));
}));
}
d.on('error', function(er) {
// an error occurred somewhere.
// if we throw it now, it will crash the program
// with the normal line number and stack message.
});
這個函數(shù)就像run
,bind
和intercept
的管道系統(tǒng),它設置有效域。它設定了域的domain.active
和process.domain
,還隱式的將域推到域模塊管理的域棧(關于域棧的細節(jié)詳見domain.exit()
)。enter函數(shù)的調用,分隔了異步調用鏈以及綁定到一個域的I/O操作的結束或中斷。
調用enter
僅改變活動的域,而不改變域本身。在一個單獨的域里可以調用任意多次Enter
和exit
。
exit
函數(shù)退出當前域,并從域的棧里移除。每當程序的執(zhí)行流程要切換到不同的異步調用鏈的時候,要保證退出當前域。調用exit函數(shù),分隔了異步調用鏈,和綁定到一個域的I/O操作的結束或中斷。
如果有多個嵌套的域綁定到當前的上下文,exit
函數(shù)將會退出所有嵌套。
調用exit
僅改變活躍域,不會改變自身域。在一個單獨的域里可以調用任意多次Enter
和exit
。
如果在這個域名下exit已經(jīng)被設置,exit將不退出域返回。
穩(wěn)定性: 0 - 拋棄。通過域里設置的錯誤事件來顯示的消除失敗的 IO 操作。
調用dispos
后,通過run,bind或intercept綁定到域的回調函數(shù)不再使用這個域,并且分發(fā)dispose
事件。
更多建議: