正文
浏览器中存在两个任务队列,一个是宏任务
一个是微任务
。但是在NodeJS
中一共存在六个事件队列
,timers
,pending callbacks
,idle prepare
,poll
,check
,close callbacks
。每一个队列里面存放的都是回调函数callback
。
这六个队列是按顺序执行的。每个队列负责存储不同的任务。
timer
里面存在的是setTimeout
与setInterval
的回调函数
pending callback
是执行操作系统的回调,例如tcp
,udp
。
idle
和 prepare
只在系统内部进行使用。一般开发者用不到
poll
执行与IO
相关的回调操作
check
中存放setImmediate
中的回调。
close callbacks
执行close
事件的回调。
在Node
中代码从上到下同步执行,在执行过程中会将不同的任务添加到相应的队列中,比如说setTimeout
就会放在timers
中, 如果遇到文件读写
就放在poll
里面,等到整个同步代码执行完毕之后就会去执行满足条件的微任务。可以假想有一个队列用于存放微任务,这个队列和前面的六种没有任何关系。
当同步代码执行完成之后会去执行满足条件的微任务,一旦所有的微任务执行完毕就会按照上面列出的顺序去执行队列当中满足条件的宏任务。
首先会执行timers
当中满足条件的宏任务,当他将timers
中满足的任务执行完成之后就会去执行队列的切换,在切换之前会先去清空微任务列表中的微任务。
所以微任务执行是有两个时机的,第一个时机是所有的同步代码执行完毕,第二个时机队列切换前。
注意在微任务中nextTick
的执行优先级要高于Promise
,这个只能死记了。
setTimeout(() => {
console.log('s1');
})
Promise.resolve().then(() => {
console.log('p1');
})
console.log('start');
process.nextTick(() => {
console.log('tick');
})
setImmediate(() => {
console.log('st');
})
console.log('end');
// start end tick p1 s1 st
setTimeout(() => {
console.log('s1');
Promise.resolve().then(() => {
console.log('p1');
})
process.nextTick(() => {
console.log('t1');
})
})
Promise.resolve().then(() => {
console.log('p2')
})
console.log('start');
setTimeout(() => {
console.log('s2');
Promise.resolve().then(() => {
console.log('p3');
})
process.nextTick(() => {
console.log('t2');
})
})
console.log('end');
// start end p2 s1 s2 t1 t2 p1 p3
Node
与浏览器事件环执行是有一些不同的。
首先任务队列数不同,浏览器一般只有宏任务和微任务两个队列,而Node
中除了微任务队列外还有6个事件队列
。
其次微任务执行时机不同,不过他们也有相同的地方就是在同步任务执行完毕之后都会去看一下微任务是否存在可执行的。对浏览器来说每当一个宏任务执行完成之后就会清空一次微任务队列。在Node
中只有在事件队列切换时才会去清空微任务队列。
最后在Node
平台下微任务执行是有优先级的,nextTick
优先于Promise.then
, 而浏览器中则是先进先出。
setTimeout(() => {
console.log('timeout');
})
setImmediate(() => {
console.log('immdieate');
})
在Node
中时而会先输出timeout
时而会先输出immdieate
,这是因为setTimeout
是需要接收一个时间参数的,如果没写就是一个0
,我们都知道无论是在Node
还是在浏览器,程序是不可能真的是0
,他会受很多的因素影响。这取决于运行的环境。
如果setTimeout
先执行就会放在timers
队列中,这样timeout
就会先输入,如果setTimeout
因为某些原因后执行了,那么check
队列中的immdieate
就会先执行。这就是为什么时而输出timeout
时而输出immdieate
。
const fs = require('fs');
fs.readFile('./a.txt', () => {
setTimeout(() => {
console.log('timeout');
}, 0)
setImmediate(() => {
console.log('immdieate');
})
})
这种情况就会一直先输出immdieate
后输出timeout
,这是因为,代码执行的时候会在timers
里面加入timeout
, 在poll
中加入fs
的回调,在check
中加入immdieate
。fs
的回调执行结束之后实在poll
队列,队列切换的时候首先会去看微任务,但是这里没有微任务就会继续向下,下面就是check
队列而不是timers
队列,所以poll
清空之后会切换到check
队列,执行immdieate
回调。