事件队列调度
Node可以通过传递回调函数将任务添加到事件队列中,这种异步的调度可以通过5种方式来实现这个目标:异步堵塞IO库(db处理、fs处理),Node内置的事件和事件监听器(http、server的一些预定义事件),开发者自定义的事件和监听器、定时器以及Node全局对象process的.nextTick()API。
3.1 异步堵塞IO库
其IO库提供的API有Node自带的Module(比如fs)和数据库驱动API,比如mongoose的.save(doc, callback)就是将繁重的数据库Insert操作以及回调函数交给子线程来操作,主线程只负责任务的调度。当MongoDB返回给Node操作结果后,回调函数才开始执行。
Dtree.create(frontData, function (err, dtree) {
if (err) {
console.log('Error: createDTree: DB failed to create due to ', err);
res.send({'success': false, 'err': err});
} else {
console.log('Info: createDTree: DB created successfully dtree = ', dtree);
res.send({'success': true, 'created_id': dtree._id.toHexString()});
}
});
比如这段处理Dtree存储的回调函数只有当事件队列中的接收到来自堵塞IO处理线程的执行完毕才会被执行。
3.2 Node内置的事件和事件监听器
Node原生的模块都预定义来一些事件,比如NET模块的一套服务状态事件。当Net中的Socket检测到close就会调用放置在事件循环中的回调函数,下例中就是将sockets数组中删除相应的socket连接对象。
socket.on('close', function(){
console.log('connection closed');
var index = sockets.indexOf(socket);
//服务器端断开相应连接
sockets.splice(index, 1);
});
3.3 开发者自定义的事件
Node自身和很多模块都支持开发者自定义事件和处理持戟处理函数,当然既然是自定义,那么触发事件也是显性地需要开发者。在Socket.io编程中就有很好的例子,开发者可以自定义消息事件来处理端对端的交互。
//socket监听自定义的事件消息
socket.on('chatMessage', function(message){
message.type = 'message';
message.created = Date.now();
message.username = socket.request.user.username;
console.log(message);
//同时也可以像对方发出事件消息
io.emit('chatMessage', message);
});
3.4 计时器(Timers)
Node使用前端一致的Timeout和Interval计时器,他们的区别在Timeout是延时执行,Interval是间隔一段事件执行。值得注意的是这组函数其实不属于JS语言标准,他们只是扩展。在浏览器中,他们属于BOM,即它的确切定义为:window.setTimeout和window.setInterval;与window.alert, window.open等函数处于同一层次。Node把这组函数放置于全局范围中。
除了这两个函数,Node还添加Immediate计时器,setImmediate()函数是没有事件参数的,在事件队列中的当前任务执行结束后执行,并且优先级比Timeout、Interbal高。
计时器的问题在于它在事件循环中并非精确的执行回调函数。《深入浅出Node.js》举了一个例子:当通过setTimeout()设定一个任务在10毫秒后执行,但是如果在9毫秒后,有一个任务占用了5毫秒的CPU,再次炖老定时器执行时,事件就已经过期了。
3.5 Node全局对象process的.nextTick()API
这个延时执行函数函数是在添加任务到队列的开头,下一次Tick周期开始时就执行,也就是在其他任务前调度。
nextTick的优先级是高于immediate的。并且每轮循环,nextTick中的回调函数全部都会执行完,而Immediate只会执行一个回调函数。这里有得说明每个Tick过程中,判断事件循环中是否有事件要处理的观察者。在Node的底层libuv,事件循环是一个典型的生产者/消费者模型。异步IO、网络请求是事件的生产者,回调函数是事件的消费者,而观察者则是在中间将传递过来的事件暂存起来。回调函数的idle观察者在每轮事件循环开始被检查,而check观察者后于idle观察者检查,两者之间被检查的就是IO操作的观察者。