事件环
setTimeout(() => {
console.log('s1 ')
Promise.resolve().then(() => {
console.log('p1')
})
Promise.resolve().then(() => {
console.log('p2')
})
})
setTimeout(() => {
console.log('s2')
Promise.resolve().then(() => {
console.log('p3')
})
Promise.resolve().then(() => {
console.log('p4')
})
})
js
浏览器中的事件环
事件环执行顺序
- 从上至下执行所有的同步代码
- 执行过程中将遇到的宏任务与微任务添加至相应的队列
- 同步代码执行完毕后,执行满足条件的微任务回调
- 每执行一个宏任务,都会检查当前批次是否有需要执行的微任务队列
- 微任务执行完毕后执行所有满足需求的宏任务回调
- 循环事件环操作
Node.js 中的事件环
组成部分
timers
pending callbacks
idle, prepare
poll
check
close callbacks
- timers: 执行 setTimeout 与 setInterval 回调
- pending callbacks:执行操作系统的回调,例如 tcp udp
- idle,prepare:只在系统内部进行使用
- poll:执行与 I/O 相关回调
- check:执行 setImmediate 中的回调
- close callbacks:执行 close 事件的回调
执行顺序
- 执行同步代码,将不同的任务添加至相应的队列
- 所有同步代码执行后会执行满足条件的微任务
- 所有微任务代码执行后会执行 timer 队列中满足的宏任务
- timer 中的所有宏任务执行完成后就会依次切换队列
- 完成队列切换之前会先清空微任务代码
我们仅需要关心 timers、poll、check 队列。
代码分析
setTimeout(() => {
console.log('s1')
})
Promise.resolve().then(() => {
console.log('p1')
})
console.log('start')
process.nextTick(() => {
console.log('tick')
})
setImmediate(() => {
console.log('setImmediate')
})
console.log('end')
// start
// end
// tick
// p1
// s1
// setImmediate
js
对于微任务来说,nextTick 的优先级要高于 Promise。
start end tick p1
s1 - timers,null - poll,setImmeriate - check
执行步骤梳理
通过一段代码分析事件循环执行步骤。
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
// t1
// p1
// s2
// t2
// p3
js
Node 与浏览器事件环对比
- 任务队列数不同
- 浏览器只有两个任务队列
- Node.js 中有 6 个事件队列
- 微任务执行时机
- 二者都会在同步代码执行完毕后执行微任务
- 微任务优先级不同
- 浏览器事件环中,微任务存放于事件队列,先进先出
- Node.js 中 process.nextTick 先于 promise.then
Node.js 常见问题
setTimeout(() => {
console.log('timeout')
})
setImmediate(() => {
console.log('immediate')
})
js
Node.js 中执行上述代码执行结果并不是唯一的,可能会先输出 timeout,也可能先输出 immediate。
const fs = require('fs')
fs.readFile('./test.txt', () => {
setTimeout(() => {
console.log('timeout')
})
setImmediate(() => {
console.log('immediate')
})
})
js
如果将上述代码包裹在一个 IO 操作中,执行顺序就固定了,结果永远是先输出 immediate,然后再输出 timeout。
这里会优先执行 poll 队列,然后再执行 check 队列,最后才能执行到 timers 事件队列。
默认情况下 setTimeout 与 setImmediate 执行顺序是随机的,因为 setTimeout 后面的延时时间是不固定的。如果将它们放到 I/O 回调中,它们的执行顺序就会变成固定的,永远都是先输出 immediate 然后再输出 timeout。