事件循环
事件循环就是node.js去做一些非阻塞I/O操作,那么问题来了,非阻塞操作又是什么呢?有一个事实对于js开发者都熟知的是,js是单线程的,也就是说在一段时间内只能够处理一种任务,其他任务要执行需要等待当前任务执行完之后再开始。
由于大部分的现代内核都是多线程的,它们能够处理不同的操作执行。当其中的一个操作完成时,内核会告诉node,回调函数将会被加入到执行队列中等待被执行。
事件循环有好几个阶段,每个阶段都有先进先出的回调队列被执行。每个阶段都有它的特别之处,当事件循环进入被给予的阶段时,它将会执行确切的操作,直到上一队列已经执行完接着执行在该阶段的回调,当该阶段上的所有队列或回调都执行完成之后,事件循环会转向下一阶段
阶段预览
- times:这个阶段执行把setTimeout()和setInterval()列入计划的回调
1. pending callbacks:执行I/O回调
2. idle, prepare: 仅为内部使用
3. poll: 获取新的I/O事件;执行I/O相关的回调(大部分关闭回调的异常)
4. check: setImmediate()回调将会被唤起
5. close callbacks: 一些关闭的回调,例如socket.on('close', ...)
times
一个timer指定了一个阈值,在一个回调被执行之后而不是你想要让它执行的确定的时间。timer的回调在给定的确定时间之后将会尽可能早的执行,然而,操作系统或者其它回调的执行可能会延迟timer的回调
pending callbacks
这个阶段执行一些系统操作的回调,例如tcp错误的类型
poll
这个poll阶段有两个主要的函数:
1. 计算它应该阻塞的时间
2. 在该队列中处理事件
当事件循环进入poll阶段,并且没有timers被安排执行,以下之一将会发生:
1. 如果poll队列不为空,事件循环将会同步迭代执行它的调用队列直到队列已经被执行完成
2. 如果poll队列是空的,以下之一将会发生:
如果脚本中有setImmediate,事件循环将会停止poll阶段,继续转向check阶段去执行这些被安排的脚本
若脚本中没有setImmediate,事件循环将会等待被添加进队列的回调,并马上执行它们
一旦poll队列是空的,事件循环将会检查那些已经到达了设置的阈值的timers。若一个或更多的timers已经准备好,事件循环将会转向执行timers阶段,从而去执行这些timers的回调函数
check
这个阶段在poll阶段已经完成之后允许人马上执行回调,如果poll阶段变为了闲置状态,且脚本中有setImmediate,事件循环将会转向check阶段而不是等待。
setImmediate实际上是一个确定的timer,它运行在一个事件循环的独立的阶段。它使用了一个libuv api,是在poll阶段完成之后,执行这些安排的回调
close callbacks
如果一个socket或者是处理突然的被关闭了,这个close事件将会在这个阶段触发。另外,它将会通过 process.nextTick触发。
setImmediate() VS setTimeout()
他们很类似,但是表现却不同,这取决于他们何时被调用
1. setImmediate()被设计为一旦当前的poll阶段完成时,执行回调
2. setTimeout()是要在设置一个最小阈值的时间之后执行回调
他们在被调用的不同环境下执行顺序会有所不同,然而,若在I/O循环之内,immediate的回调总是先执行
const fs = require('fs');
fs.readFile(__filename, () ={
setImmediate(() ={
console.log('immediate');
});
setTimeout(() ={
console.log('timeout');
}, 0);
});
process.nextTick()
尽管是异步api的一部分,但不是事件循环的部分。这个nextTickQueue将会在当前操作完成之后被处理,不管当前事件循环处在哪一阶段
setImmediate() VS process.nextTick()
1. 在同一阶段process.nextTick()将会立即触发执行
2. setImmediate()在下一次迭代循环中触发
为什么使用process.nextTick()
1. 允许用户处理错误,在事件循环继续之前清除不需要的资源或者是再次发出请求
2. 有时候需要在执行栈结束时但要在事件循环继续之前执行该回调函数
举个栗子,如果要在函数构造器中触发一个事件,若按照以下写法,是不会被调用执行的,因为该代码并没有被处理,构造函数还没有被执行完成。
const EventEmitter = require('events');
const util = require('util');
function MyEmitter() {
EventEmitter.call(this);
this.emit('event'); // 该事件触发无效
}
util.inherits(MyEmitter, EventEmitter);
const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
console.log('an event occurred!');
});
若使用了process.nextTick(),就可以在构造器执行完成之后触发该事件,从而触发回调
const EventEmitter = require('events');
const util = require('util');
function MyEmitter() {
EventEmitter.call(this);
// use nextTick to emit the event once a handler is assigned
process.nextTick(() => {
this.emit('event');
});
}
util.inherits(MyEmitter, EventEmitter);
const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
console.log('an event occurred!');
});
参考资料:https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/