内容简介:考察如下代码,脑回路中运行并输出结果:JS 是单线程,朴素地讲,同时只能完成一件事件。如果有耗时的任务,那后续的所有任务都要等待其完成才能执行。为了避免这种阻塞,引入了事件循环。即,将代码的执行分成一个个很小的阶段(一次循环),每个阶段重复相应的事情,直到所有任务都完成。
问题
考察如下代码,脑回路中运行并输出结果:
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
至此,走完了所有代码。
结果
以下是文章开头的结果:
1 async start bar 7 6 async end 8 2 4 3 5 9 11 10 12 immediate
参考
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 理解原型其实是理解原型链
- 要理解深度学习,必须突破常规视角去理解优化
- 深入理解java虚拟机(1) -- 理解HotSpot内存区域
- 荐 【C++100问】深入理解理解顶层const和底层const
- 深入理解 HTTPS
- 深入理解 HTTPS
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。