在node中,事件循环表现出来的状态和浏览器大体相同,可是node有一套本身的模型。javascript
node事件循环依靠libuv引擎,node选择chrome v8 做为js的解释器,v8将js代码分析后,去掉用node相关的api,这些api最后由libuv引擎驱动,执行对应任务,把不一样事件放在不一样队列等待主线程执行。java
因此,实际上node中的事件循环存在于libuv引擎中。node
node中大体的事件循环顺序chrome
外部输入数据---轮询阶段(poll)---检查阶段(check)---关闭事件回调阶段(close callback)---定时器检查阶段(timer)---I/O事件回调阶段(I/O callback)---闲置阶段(idle,prepare)---轮询阶段...api
当v8引擎将js代码解析后传入libuv引擎,首先计入poll阶段。浏览器
poll阶段执行逻辑:socket
先查看poll queue 是否有事件,有任务,按照先进先出依次执行回调。tcp
当queue为空,会检查是否有 setImmediate()的callback,若有,进入check阶段的callback。同时也会检查是否有到期的timer,若是有,就把到期的timer的callback按照调用顺序,放入到 timer queue 中,以后循环进入 timer 阶段的queue 中的callback。函数
这二者顺序是不固定的,受运行环境影响。若是二者的queue都是空的,loop就在poll阶段停留,直到有一个I/O事件返回。oop
poll阶段执行poll queue中的回调实际上不会无限执行过下去。当 1 全部回调执行完毕 2 执行数超过了node限制,poll阶段会终止执行 poll queue中的下一个回调。
专门用来执行 setImmediate()方法的回调,当poll阶段进入空闲,而且 setImmediate()里面有callback,事件循环进入这个阶段。
当一个socket链接或者一个handle关闭(socket.destory())close事件会在这个阶段执行回调。不然事件会用 process.nextTick()方法发送出去。
这个阶段以先进先出的方式执行全部到期的timer加入到timer队列里面的callback,一个timer callback 指的是 经过 setTimeout或者 setInterval 函数设置的回调函数。
这个阶段主要执行大部分I/O事件的回调,包括一些操做系统执行的回调。
如:一个tcp链接出错了,系统执行回调捕获错误报告
node中存在一种特殊的队列,nextTick queue.
这个队列回调执行虽然没有被表示为一个阶段,可是这些时间会在每个阶段完毕准备进入下一个阶段时优先执行。 当事件循环进入下一个阶段以前,会先检查 nextTick queue是否有任务,若是有,会先清空这个队列。不过须要注意这个操做在队列清空前是不会中止的,因此,使用不当,会致使死循环,直至内存泄漏。
setTimeout()是定义一个回调,但愿在必定时间后,第一时间去执行。but,受到各类影响,该回调并不会在时间间隔后,精准执行。node会在能够执行timer回调的第一时间去执行你所设置的回调任务。
setImmediate(),字面上看,是当即执行。实际上,他会在一个固定的阶段才会执行回调,即poll阶段以后。
谁会先执行????
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
复制代码
答案是不必定,这取决于代码运行环境。运行环境可能致使同步队列里面两个方法顺序随机决定。
可是,在I/O事件的回调中,下面代码顺序是始终不会变的。
const fs = require('fs');
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
});
答案永远是不变的
immediate
timeout
复制代码
由于在I/O事件的回调中,setImmediate方法的回调永远在timer的回调前执行。