We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
NodeJS 的事件循环(EventLoop)和浏览器是不一样的,NodeJS 使用 libuv 实现事件循环和所有异步行为。
┌───────────────────────────┐ ┌─>│ timers │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ pending callbacks │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ idle, prepare │ │ └─────────────┬─────────────┘ ┌───────────────┐ │ ┌─────────────┴─────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └─────────────┬─────────────┘ │ data, etc. │ │ ┌─────────────┴─────────────┐ └───────────────┘ │ │ check │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ └──┤ close callbacks │ └───────────────────────────┘
查看 libuv 对上述流程的实现
从官方流程图可以发现,NodeJS 的事件循环(EventLoop)是多任务队列,事件循环(EventLoop)将按照上图流程进入逐个阶段。
setTimeout 和 setInterval 的回调会进入本阶段队列。 事件循环进入本阶段,会检查并执行到时的计时器回调,如果没有,结束此阶段。
setTimeout
setInterval
该阶段执行上一轮循环被延迟的某些系统操作回调(比如 TCP 错误)。
提供给 NodeJS 内部使用。
除 close 外的所有 I/O 回调会被推进该阶段的队列。
close
本阶段会计算需要阻塞和等待 I/O 的时间,并按下面步骤处理:
setImmediate
该阶段被称为轮询(Poll)的原因大概是,几乎所有的异步回调都在本阶段处理,并且会阻塞等待新的异步任务,根据新的任务类型发生阶段流转。
执行 setImmediate 注册的回调。
执行 I/O 的 close 回调,如果没有,则结束本阶段。
案例:
setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); }); // 输出 ?
setImmediate 回调在 Check 阶段执行,setTimeout 回调在 Timer 阶段执行,理论上,setTimeout 回调应该更早执行,实际上上述代码输出是随机的,这与系统和进程性能有关。
因为 setTimeout 指定的时间是有下限的,虽然指定了 0,但是最小只能是 1ms
When delay is larger than 2147483647 or less than 1, the delay will be set to 1. Non-integer delays are truncated to an integer. from NodeJS Timer 文档,
When delay is larger than 2147483647 or less than 1, the delay will be set to 1. Non-integer delays are truncated to an integer.
from NodeJS Timer 文档,
如果把它们放入一个 I/O 回调的话,则 setImmediate 一定会先执行。
fs.readFile('/path/to/file', () => { setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); }); }); // 输出 immediate -> timeout
上述代码会进入 Poll 阶段,等待执行 readFile 回调,添加 setImmediate 和 setTimeout 回调,查看 Poll 阶段处理步骤,此时 I/O 队列为空,setImmediate 队列存在,因此进入 Check 阶段执行回调,然后在下一次循环执行 setTimeout 的回调。
readFile
虽然 process.nextTick 是异步 API 之一,但从技术上来说它不属于事件循环(EventLoop)的一部分。process.nextTick 会中断事件循环(EventLoop),不管在事件循环(EventLoop)的任何时刻,当前操作完成后(即执行栈为空时),会执行 nextTickQueue 的所有回调,然后再继续进行事件循环(EventLoop)。
process.nextTick
nextTickQueue
NodeJS 对操作的定义: an operation is defined as a transition from the underlying C/C++ handler, and handling the JavaScript that needs to be executed.
const fs = require('fs'); process.nextTick(() => { console.log('nextTick1'); }); setTimeout(() => { console.log('setTimeout1'); process.nextTick(() => { console.log('nextTick2'); }); }); setTimeout(() => { console.log('setTimeout2'); }); fs.readFile('./index.js', () => { console.log('readFile'); });
执行过程:
'nextTick1'
setTimeout1
'nextTick2'
setTimeout2
解释:
nextTick2
setImmediate 的含义是“立即”,容易让人以为比 process.nextTick 先执行,这个命名是一个历史问题。
实际上,上面已经提到过,setImmediate 是事件循环的一部分,它在循环即将结束的 Check 阶段执行,而 process.nextTick 无论何时,只要当前操作结束就会被执行,它们之间并没有太多联系。
NodeJS 的官方文档并没有把 微任务(microtask) 写入事件循环,查阅了一些资料,猜测可能是以下原因:
微任务(microtask)是 WHATWG 中的概念,浏览器只有一个任务队列(task queue),任务(task)并无优先级,而微任务队列(microtask queue)提供了优先级。最初微任务(microtask)也是提供给 Promise/A+ 的 then 函数实现使用,后续更多的浏览器 API 被实现以微任务(microtask)执行。
then
NodeJS 实现事件循环时是多队列的,所有异步回调有着优先级调度。而对于 Promise.then 函数的实现,在上述事件循环的某个阶段后执行,也是符合 ES6 规范和Promise/A+ 规范的,除此之外并没有其他需要微任务(microtask)。
Promise.then
所以 NodeJS 使用了微任务(microtask)的语义,但并没有按 WHATWG 规范实现微任务队列。
但是在 NodeJS 11 后,修改了 process.nextTick 和 Promise.then 的执行时机,并且增加 queueMicrotask API:
queueMicrotask
timers: run nextTicks after each immediate and timer #22842. MacroTask and MicroTask execution order #22257. implement queueMicrotask #22951.
微任务(microtask)会在 process.nextTick 执行完后执行。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
NodeJS EventLoop 的流程
查看 libuv 对上述流程的实现
从官方流程图可以发现,NodeJS 的事件循环(EventLoop)是多任务队列,事件循环(EventLoop)将按照上图流程进入逐个阶段。
1 - Timer 阶段
setTimeout
和setInterval
的回调会进入本阶段队列。事件循环进入本阶段,会检查并执行到时的计时器回调,如果没有,结束此阶段。
2 - Pending callbacks 阶段
该阶段执行上一轮循环被延迟的某些系统操作回调(比如 TCP 错误)。
3 - Idle, prepare 阶段
提供给 NodeJS 内部使用。
4 - Poll 阶段
除
close
外的所有 I/O 回调会被推进该阶段的队列。本阶段会计算需要阻塞和等待 I/O 的时间,并按下面步骤处理:
setImmediate
回调队列,如果有,进入 Check 阶段。该阶段被称为轮询(Poll)的原因大概是,几乎所有的异步回调都在本阶段处理,并且会阻塞等待新的异步任务,根据新的任务类型发生阶段流转。
5 - Check 阶段
执行
setImmediate
注册的回调。6 - Close callbacks 阶段
执行 I/O 的
close
回调,如果没有,则结束本阶段。容易混淆的概念
setImmediate vs setTimeout
案例:
setImmediate
回调在 Check 阶段执行,setTimeout
回调在 Timer 阶段执行,理论上,setTimeout
回调应该更早执行,实际上上述代码输出是随机的,这与系统和进程性能有关。因为
setTimeout
指定的时间是有下限的,虽然指定了 0,但是最小只能是 1mssetTimeout
的回调未到时,最后到 Check 阶段执行setImmediate
回调,然后再第二次循环才能执行;setTimeout
的回调会先执行。如果把它们放入一个 I/O 回调的话,则
setImmediate
一定会先执行。上述代码会进入 Poll 阶段,等待执行
readFile
回调,添加setImmediate
和setTimeout
回调,查看 Poll 阶段处理步骤,此时 I/O 队列为空,setImmediate
队列存在,因此进入 Check 阶段执行回调,然后在下一次循环执行setTimeout
的回调。process.nextTick
虽然
process.nextTick
是异步 API 之一,但从技术上来说它不属于事件循环(EventLoop)的一部分。process.nextTick
会中断事件循环(EventLoop),不管在事件循环(EventLoop)的任何时刻,当前操作完成后(即执行栈为空时),会执行nextTickQueue
的所有回调,然后再继续进行事件循环(EventLoop)。案例:
执行过程:
nextTickQueue
有回调,执行输出'nextTick1'
setTimeout1
nextTickQueue
有回调,执行输出'nextTick2'
setTimeout2
readFile
解释:
nextTick2
在setTimeout2
之前说明了不管什么时候,执行栈为空,先执行nextTickQueue
。nextTick2
在readFile
之前说明了仍然在本次循环中。process.nextTick
vssetImmediate
setImmediate
的含义是“立即”,容易让人以为比process.nextTick
先执行,这个命名是一个历史问题。实际上,上面已经提到过,
setImmediate
是事件循环的一部分,它在循环即将结束的 Check 阶段执行,而process.nextTick
无论何时,只要当前操作结束就会被执行,它们之间并没有太多联系。关于微任务(microtask)
NodeJS 的官方文档并没有把 微任务(microtask) 写入事件循环,查阅了一些资料,猜测可能是以下原因:
微任务(microtask)是 WHATWG 中的概念,浏览器只有一个任务队列(task queue),任务(task)并无优先级,而微任务队列(microtask queue)提供了优先级。最初微任务(microtask)也是提供给 Promise/A+ 的
then
函数实现使用,后续更多的浏览器 API 被实现以微任务(microtask)执行。NodeJS 实现事件循环时是多队列的,所有异步回调有着优先级调度。而对于
Promise.then
函数的实现,在上述事件循环的某个阶段后执行,也是符合 ES6 规范和Promise/A+ 规范的,除此之外并没有其他需要微任务(microtask)。所以 NodeJS 使用了微任务(microtask)的语义,但并没有按 WHATWG 规范实现微任务队列。
但是在 NodeJS 11 后,修改了
process.nextTick
和Promise.then
的执行时机,并且增加queueMicrotask
API:微任务(microtask)会在
process.nextTick
执行完后执行。参考资料
The text was updated successfully, but these errors were encountered: