# 其他常见问题
【讨论题】谈一谈 Node 和 Web 的事件循环(EventLoop)机制的异同
题目描述: 通过前面章节,我们了解到 Lerna 源码中利用了 EventLoop 机制将脚手架初始化和命令执行逻辑解耦,这是 EventLoop 的一个典型应用,然而 Node.js 的 EventLoop 和 Web 的 EventLoop 并不相同,你知道他们之间有哪些差异吗?你是否在实际项目中应用过 Node.js 的 setImmediate 和 process.nextTick 方法?如果有,请分享你在哪些场景下使用了这些方法,并指出你为什么在这种场景使用,它会带来什么好处?
关键提炼:
- Node.js 事件循环:http://nodejs.cn/learn/the-nodejs-event-loop
- 什么是 process.nextTick:http://nodejs.cn/learn/understanding-process-nexttick
- 什么是 setImmediate:http://nodejs.cn/learn/understanding-setimmediate
- Web 和 Node.js 事件循环对比:http://www.ruanyifeng.com/blog/2014/10/event-loop.html
#
Nodejs
事件轮询 (opens new window)共六阶段: timer -> Pending I/O callbacks -> Idle, prepare -> Poll -> Check -> Close callbacks
# timer
定时器阶段,处理
setTimeout
和setInterval
的回调函数。进入这个阶段,主线程会检查当前时间是否满足定时器的条件,满足则执行回调函数,否则离开这个阶段。# Pending I/O callbacks
除了以下操作
setTimeout()
和setInterval()
的回调函数。(它们在 timer 阶段执行)setImmediate()
的回调函数。(它在 Check 阶段执行)用于关闭请求的回调函数,例如
socket.on('close', ...)
。(它是在 Close callbacks 阶段执行)其他的回调函数都是在这个阶段执行。
# Idle, prepare
该阶段只供
libuv
内部调用,可以忽略。# Poll
这个阶段是轮询阶段,用于等待还未返回的 I/O 事件,例如服务器的回应,用户鼠标事件等等。这个阶段耗时会比较长。如果没有其他异步任务要处理(比如到期的定时器),会一直停留在这个阶段,等待 I/O 请求返回结果。
# Check
该阶段执行
setImmediate()
的回调函数。# Close callbacks
该阶段执行关闭请求的回调函数。比如
socket.on('close', ...)
。
# 微任务 VS 宏任务
为了解决同步问题(前一个操作耗时问题,导致不能再执行下去),JavaScript 引入了异步的概念。除了主线程上的同步操作外,JavaScript 还有个任务队列(task queue)。所有的大开销的任务我们都可以通过异步函数包裹一下,通过回调函数的方式去触发,我们只需要先把大开销的操作任务扔到任务队列中,等待主线程上的同步任务执行完毕后,再去轮询任务队列中的事件,满足执行条件的事件放到主线程上去执行。
js
将异步任务划分为微任务和宏任务,微任务会在宏任务之前执行(因为每次从主线程切换到任务队列时,都会优先遍历微任务队列,后遍历宏任务队列)。 注意:只有微任务队列为空时,才会遍历宏任务队列,并且每次从主线程切入任务队列时,都会优先遍历一边微任务队列。
setTimeout(() => { // 宏任务 console.log(4) }, 0) new Promise(resolve => { resolve() console.log(1) }).then(data => { // 微任务 console.log(3) }) console.log(2) // 执行结果: 1 2 3 4
1
2
3
4
5
6
7
8
9
10
11
12
13
14宏任务 Macro Task 微任务 Micro Task 名称 浏览器 Node 名称 浏览器 Node I/O 操作 √ √ process.nextTick()
(优先推荐使用queueMicroTask()
)× √ setTimeout
√ √ Promise.then catch finally
√ √ setInterval
√ √ MutationObserver
(接口提供了监视对 DOM 树所做更改的能力)√ × setImmediate
× √ requestAnimationFrame
告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。√ × #
process.nextTick
node 中特有的异步方法,属于微任务,但是在微任务中,它的执行时机是最早的,比
Promise.then
还早。#
setImmediate
node 中特有的异步操作,只支持 node 端,属于宏任务。
#
setTimeout VS setImmediate
setTimeout 和 setImmediate 都是用于调度异步任务的函数,但它们在执行时机上有所不同。以下是它们的区别和使用场景:
setTimeout
用法:
setTimeout(callback, delay)
1
# Nodejs VS Web
事件循环
# 进程&线程
线程和进程:线程时最小的执行单位,而进程有至少一个线程组成。
← 性能优化