var http = require('http');
var connect = require('connect');
var RedisStore = require('connect-redis')(connect);
var domainMiddleware = require('domain-middleware');
var server = http.createServer();
var app = connect();
app.use(connect.session({
key: 'key',
secret: 'secret',
store: new RedisStore(6379, 'localhost')
}));
//domainMiddleware的使用可以看前面的链接
app.use(domainMiddleware({
server: server,
killTimeout: 30000
}));
此时,当我们的业务逻辑代码中出现了异常,发现竟然没有被domain捕获!经过一番尝试,终于将问题定位到了:
var domain = require('domain');
var redis = require('redis');
var cache = redis.createClient(6379, 'localhost');function error() {
cache.get('a', function () {
throw new Error('something wrong');
});
}
function ok () {
setTimeout(function () {
throw new Error('something wrong');
}, 100);
}
var d = domain.create();
d.on('error', function (err) {
console.log(err);
});
d.run(ok); //domain捕获到异常
d.run(error); //异常被抛出
奇怪了!都是异步调用,为什么前者被捕获,后者却没办法捕获到呢?
Domain剖析
回过头来,我们来看看domain做了些什么来让我们捕获异步的请求(代码来自node v0.10.4,此部分可能正在快速变更优化)。
node事件循环机制
在看Domain的原理之前,我们先要了解一下nextTick和_tickCallback的两个方法。
function laterCall() {
console.log('print me later');
}process.nextTick(laterCallback);
console.log('print me first');
上面这段代码写过node的人都很熟悉,nextTick的作用就是把laterCallback放到下一个事件循环去执行。而_tickCallback方法则是一个非公开的方法,这个方法是在当前时间循环结束之后,调用之以继续进行下一个事件循环的入口函数。
换而言之,node为事件循环维持了一个队列,nextTick入队,_tickCallback出列。
domain的实现
在了解了node的事件循环机制之后,我们再来看看domain做了些什么。
domain自身其实是一个EventEmitter对象,它通过事件的方式来传递捕获的错误。这样我们在研究它的时候,就简化到两个点:
什么时候触发domain的error事件:
进程抛出了异常,没有被任何的try catch捕获到,这时候将会触发整个process的processFatal,此时如果在domain包裹之中,将会在domain上触发error事件,反之,将会在process上触发uncaughtException事件。
domain如何在多个不同的事件循环中传递:
当domain被实例化之后,我们通常会调用它的run方法(如之前在web服务中的使用),来将某个函数在这个domain示例的包裹中执行。被包裹的函数在执行的时候,process.domain这个全局变量将会被指向这个domain实例。当这个事件循环中,抛出异常调用processFatal的时候,发现process.domain存在,就会在domain上触发error事件。









