故事的开始
由于各种原因,没有继续写V8引擎系列文章有相当长的一段时间了,今天本来不打算写解剖node的文章的,但一个contextify
被扰乱的native问题实在引起了我对它的兴趣,好吧,我又重新回归了这个神奇的世界,看了一些文档资料,只能有个大概的印象,然而作为一个独操引擎的男人,既然入了坑,怎么能浅尝则止呢,结合源代码,我仔细地观察了eventloop
,领教了这个神奇的存在给node注入了强大的生命力,所以一个node的eventloop
到底干了什么事情呢,将在本文一一为大家揭晓。
首先,我们先来看看一个同样带有Event字样的“类“eventloop。
EventEmitter & Eventloop
很多人搞不清EventEmitter跟loop的关系,甚至还有混为一谈者,不过在应用层的nodeAPI确实容易让人造成误解,毕竟很多异步事件的监听,处理都是通过emitter来完成的,像这样
//socket inherited from EventEmitter
socket.on('myEvent', () => {
//handler
});
这种代码写起来很有异步风格,其实如果我们开发一个网络IO的程序确实也会感觉这很异步,其实,EventEmitter的API是完全同步的,它的模型也十分简单, OK,我来简单模拟一下emit里面发生的事情。
EventEmitter.prototype.emit = function(event_type) {
//main code
//handle some TypeErrors
//get all events registered in Emitter
events = this._events;
//type used for the key to find which event handler should be invoked
handle = events[event_type];
var isFn = typeof handler === 'function';
len = arguments.length;
switch (len) {
// fast cases
case 1:
//handle.call(self) in emitNone
emitNone(handler, isFn, this);
break;
case 2:
emitOne(handler, isFn, this, arguments[1]);
break;
case 3:
emitTwo(handler, isFn, this, arguments[1], arguments[2]);
break;
case 4:
emitThree(handler, isFn, this, arguments[1], arguments[2], arguments[3]);
break;
// slower back to normal state without Inline cache optimization
default:
emitMany(handler, isFn, this, args);
}
}
现在应该可以很清楚的看到emit并没有任何的异步迹象,其实从node的event模块并没有依赖任何的native模块就可以猜出来这个API就是纯同步的,我再来写一段更能揭露它真面目的代码。
const util = require('util');
const EventEmitter = require('events');
function MyEmitter() {
//initialize the EventEmitter for the listeners queue
EventEmitter.call(this);
//do something first
// this.emit('event_x'); handler can not receive this event because it emitted synchronously when MyEmitter instantiated
//it works
setImmediate(emitInCallback, this, new Error('temporary error instance to preserve lost call stack'));
}
function emitInCallback(self, err) {
if(err) {
self.emit('error_event');
return;
}
self.emit('event_x');
}
util.inherits(MyEmitter, EventEmitter);
let em = new MyEmitter();
em.on('event_x', () => {
//but the emitter API is synchronously
logger.log('I can receive this asynchronously event ');
});
em.on('error_event', () => {
logger.log('I can receive this error event ');
});
可以看到,如果我们直接在constructor上调用emit
,很遗憾并不能接受到事件,那是因为事件在实例化的那一瞬间已经触发,由于不是异步代码,所以只有在触发的那一瞬间立即调用了handler,这个机会一旦错过便并不再有,但我就是要任性地把事件的触发放在constructor上,好的,我需要把它手动变成异步代码,只需要添加到事件循环最后一帧(phase
)的check event
,即调用setImmediate
函数,然后稍等一会,没错,在运行这段代码并不是马上给我们呈现log,我们需要等待一段微不足道执行event loop前面一些帧的额外CPU时间,虽然在v8运行我们的代码时,回调的触发函数,我以后的文章会叫做这是一个Stub
,即js函数在引擎层的表示,好的,这个Stub现在执行期的入口被注册,然后等到这一轮loop快要结束时执行Stub缓存的代码,关于这个函数的更多细节我会结合源代码给大家分享。
不过,这个异步调用需要注意一个异常处理的问题,原因很简单,这种调用会导致调用栈帧的缺失,看着debugger上的调用栈根本对错误手足无措,上面的代码在调用时临时实例化了一个Error对象,手动保护栈帧缺失。
OK,这里我们分清楚了同步的API跟依赖于native层eventloop的异步的代码行为,这就是个存在于native层的过程,所以我们要研究它,必须深入native层进行分析,下一篇将带领大家结合源代码进行解剖~~