本文共 3191 字,大约阅读时间需要 10 分钟。
问题 考察如下代码,脑回路中运行并输出结果: console.log("1");setTimeout(function setTimeout1() { console.log("2"); process.nextTick(function nextTick1() { console.log("3"); }); new Promise(function promise1(resolve) { console.log("4"); resolve(); }).then(function promiseThen1() { console.log("5"); }); setImmediate(function immediate1() { console.log("immediate"); });});process.nextTick(function nextTick2() { console.log("6");});function bar() { console.log("bar");}async function foo() { console.log("async start"); await bar(); console.log("async end");}foo();new Promise(function promise2(resolve) { console.log("7"); resolve();}).then(function promiseThen2() { console.log("8");});setTimeout(function setTimeout2() { console.log("9"); new Promise(function promise3(resolve) { console.log("11"); resolve(); }).then(function promiseThen3() { console.log("12"); }); process.nextTick(function nextTick3() { console.log("10"); });}); JS 事件循环 JS 是单线程,朴素地讲,同时只能完成一件事件。如果有耗时的任务,那后续的所有任务都要等待其完成才能执行。 为了避免这种阻塞,引入了事件循环。即,将代码的执行分成一个个很小的阶段(一次循环),每个阶段重复相应的事情,直到所有任务都完成。 一个阶段包含以下部分: - Timers:到期的定时器任务,
setTimeout ,setInterval 等注册的任务。 - IO Callbacks:IO 操作,比如网络请求,文件读写。
- IO Polling:IO 任务的注册
- Set Immediate:通过
setImmediate 注册的任务 - Close:
close 事件的回调,比如 TCP 的断开。 Ticks and Phases of the Node.js Event Loop 图片来自 Daniel Khan 的 Medium 博客,见文末 同步代码及上面每个环节结束时都会清空一遍微任务队列,记住这点很重要! 代码执行流程 执行的流程是, - 将代码顺序执行。
- 遇到异步任务,将任务压入待执行队列后继续往下。
- 完成同步代码后,检查是否有微任务(通过
Promise ,process.nextTick ,async/await 等注册),如果有,则清空。 - 清空微任务队列后,从待执行队列中取出最先压入的任务顺序执行,重复步骤一。
另, async/await 本质上是 Promise ,所以其表现会和 Promise 一致。 process.nextTick 注册的回调优先级高于定时器。 setImmediate 可看成 Node 版本的 setTimeout ,所以可与后者同等对待。 示例代码分析 Round 1 - 首先遇到同步代码
console.log(1) ,立即执行输出 1 - 接下来是一个
setTimeout 定时器,将其回调压入待执行队列 [setTimeout1] - 遇到
process.nextTick ,将其回调 nextTick2 压入微任务队列 [nextTick2] - 然后是 async 函数
foo 的调用,立即执行并输出 async start - 然后是
await 语句,这所在的地方会创建并返回 Promise,所以这里会执行其后面的表达式,也就是 bar() 函数的调用。 - 执行
bar 函数,输出 bar - 在执行了
await 后面的语句后,它所代表的 Promise 就创建完成了,foo 函数体后续的代码相当于 promise 的 then ,放入微任务队列 [nextTick2, rest_of_foo] - 继续往下遇到
new Promise ,执行 Promise 的创建输出 7 ,将它的 then 回调压入微任务队列 [nextTick2, rest_of_foo,promiseThen2] - 遇到另一个
setTimeout ,回调压入待执行队列 [setTimeout1,setTimeout2] - 至此,代码执行完了一轮。此时的输出应该是
1, async start, bar,7 Round 2 - 查看微任务队列,并清空。所以依次执行
[nextTick2, rest_of_foo,promiseThen2] ,输出 6,async end,8 。 Round 3 - 查看待执行队列
[setTimeout1,setTimeout2] ,先执行 setTimout1 - 遇到
console.log(2) 输出2 - 遇到
process.nextTick 将 nextTick1 压入微任务队列 [nextTick1] - 遇到
new Promise 立即执行 输出 4 ,执行 resolve() 后将 promiseThen1 压入微任务队列 [nextTick1,promiseThen1] - 遇到
setImmediate 将回调压入待执行队列 [setTimeout2,immediate1] - 此时
setTimeout1 执行完毕,此时的输出应该为 2,4 Round 4 - 检查微任务队列
[nextTick1,promiseThen1] 依次执行并输出 3,5 Round 5 - 检查待执行队列
[setTimeout2,immediate1] ,执行 setTimeout2 - 遇到
console 输出 9 - 遇到
new Promise 执行并输出 11 ,将 promiseThen3 压入微任务队列 [promiseThen3] - 遇到
process.nextTick 将 nextTick3 压入微执行队列。注意,因为 process.nextTick 的优化级高于 Promise,所以压入后的结果是: [nextTick3,promiseThen3] - 此时
setTimeout2 执行完毕,输出为 9,11 Round 6 - 检查微任务队列
[nextTick3,promiseThen3] 执行并输出 10,12 Round 7 - 检查待执行队列
[immediate1] ,执行并输出 immediate 至此,走完了所有代码。 结果 以下是文章开头的结果: 1async startbar76async end824359111012immediate 参考 |
转载于:https://www.cnblogs.com/Wayou/p/understanding_event_loop.html