Node.js 将各种函数( 也叫任务或回调 )分成至少 6 类,按先后顺序调用,因此将时间分为六个阶段:
- timers 阶段(setTimeout/setInterval)
- I/O callbacks 该阶段不用管
- idle, prepare 该阶段不用管
- poll 轮询阶段,停留时间最长,可以随时离开。
- 主要用来处理 I/O 事件,该阶段中 Node 会不停询问操作系统有没有文件数据、网络数据等
- 如果 Node 发现有 timer 快到时间了或者有 setImmediate 任务,就会主动离开 poll 阶段
- check 阶段,主要处理 setImmediate 任务
- close callback 该阶段不用管
Node.js 会不停从 1 ~ 6 循环处理各种事件,这个过程叫做事件循环(Event Loop)
nextTick
process.nextTick(fn) 的 fn 会在什么时候执行?
- 在 Node.js 11 之前,会在每个阶段的末尾集中执行(俗称队尾执行)
- 在 Node.js 11 之后,会在每个阶段的任务间隙执行(俗称插队执行)
浏览器跟 Node.js 11 之后的情况类似。
nextTick 只在 Node.js 中有
浏览器可用 window.queueMicrotask 模拟 nextTick
setTimeout(() => console.log('timeout1'));
setTimeout(() => {
console.log('timeout2')
// Node.js 要换成 process.nextTick
window.queueMicrotask(() => {
console.log('nextTick')
})
});
setTimeout(() => console.log('timeout3'));
setTimeout(() => console.log('timeout4'));
// Node.js 11 之前结果是:timeout1 timeout2 timeout3 timeout4 nextTick
// Node.js 11 之后及浏览器:timeout1 timeout2 nextTick timeout3 timeout4
Promise.then
Promise.resolve(1).then(fn) 的 fn 会在什么时候执行?
这要看 Promise 源码是如何实现,一般都是用 process.nextTick(fn) 实现的,所以直接参考 nextTick.
async / await
这是 Promise 的语法糖,所以直接转为 Promise 写法即可
实践
- 下面代码的执行顺序 ```javascript setTimeout(() => { console.log(‘setTimeout’) })
setImmediate(() => { console.log(‘setImmediate’) })
// 在 Node.js 中, 不能确定上面的输出顺序 // EventLoop 是用 C++ 写的,这取决于 C++ 和 JS 哪个先启动
setImmediate 在浏览器中无效,可用改用 window.queueMicrotask, 微任务比宏任务先执行
```javascript
setTimeout(() => {
console.log('setTimeout')
})
window.queueMicrotask(() => {
console.log('setImmediate')
})
// setImmediate setTimeout
- 下面代码的输出顺序 ```javascript async function async1() { console.log(‘1’) await async2() console.log(‘2’) }
async function async2() { console.log(‘3’) }
console.log(‘4’)
setTimeout(function(){ console.log(‘5’) }, 0)
async1()
new Promise(function(resolve){ console.log(‘6’) resolve() }).then(function(){ console.log(‘7’) })
console.log(‘8’)
``` 解析:
- 函数定义直接跳过,先执行
console.log('4')
- setTimeout 放入 timer 队列等待执行
timer = ['5']
- 执行 async1 函数
- 执行
console.log('1')
await async2(); console.log('2')
等价于async2().then( console.log('2') )
, 即 await 后的代码都放到 .then 内,先执行 async2 函数,console.log('3')
,.then 中的console.log('2')
放入 nextTick 队列next = ['2']
- 执行
- Promise 中的函数 立刻执行,故执行
console.log('6')
,.then 中的console.log('7')
放入 nextTick 队列,next = ['2', '7']
- 执行
console.log('8')
- 此时 poll 完毕,优先执行 nextTick,输出 2, 7
- 然后进入 timer ,输出 5
- 结果为 4,1,3,6,8,2,7,5