前言
标题党了一点,但是不耽误你巩固事件循环的知识呀。注意是巩固,本文有几点易混的地方,不适合对事件循环一点都不懂的新手,不然看完可能就傻了。
再次警告🔥🔥🔥: 看完你可能更糊涂,虽说这也达到了我的效果
代码
function test(){
console.log(0);
setTimeout(function() {
console.log(1);
async function async1() {
await async2()
console.log(2)
}
async function async2() {
console.log(3)
}
async1()
new Promise(function(resolve) {
console.log(4)
resolve()
}).then(function(){
console.log(5)
})
})
new Promise(function(resolve) {
console.log(6);
resolve();
}).then(function() {
console.log(7)
}).then(function() {
console.log(8)
})
setTimeout(function() {
new Promise(function(resolve) {
console.log(9);
resolve();
}).then(function() {
console.log(10)
})
})
return console.log(11)
}
test()
上述代码总共有12个console.log,涉及到了关于async,await的一个易混点,还有本文最想告诉你的几个事件循环的知识点。
首先很明显的是,这段代码可以拆分成五个部分来看,一个console.log(1),两个setTimeout,一个new Promise,以及一个return。我们都知道,第一个输出的肯定是"0",因为他在test()调用的第一个宏任务----也就是运行这段函数代码的JS主线程上。剩下来的分析却要从三个方面来讲:
new Promise()
👀第一个先讲最基本的,中间那段的new Promise():
new Promise(function(resolve) {
console.log(6);
resolve();
}).then(function() {
console.log(7)
}).then(function() {
console.log(8)
})
很明显,这个Promise后面没有同步的console.log代码了。按照我们熟悉的,两个setTimeout都会进入宏任务队列等待,所以我们暂且不看。前面的0已经打印了,紧跟着是6也是没有疑问的(有疑问请出门右转找高赞的事件循环文章),那么接下来呢?
照理说只有两种可能,一种是打印7,因为这是第一个进入微任务队列的回调;另一种是打印11,因为这是目前JS主线程运行的唯一函数的返回值。因此问题又回到了是微任务队列清空之后函数返回呢?还是主进程清空之后再循环微任务队列呢?
答案是后者,主进程不空闲,即test函数不返回的话,微任务队列是不会循环的。即使你在浏览器的控制窗口看见一个很有意思的事:
关于这段如果不理解,你还可以去这个视频看这段关于事件循环的演讲。作者在最后五分钟举例的点击按钮和JS按钮执行的API对同一段代码不同执行顺序的讲解。
剩下的就没什么好说了,毕竟上面的图都暴露了,在我们执行第一个then的时候,第二个then又会进微任务队列,而直到微任务队列清空,主进程才回去宏任务队列拿下一个宏任务。
所以前几个的顺序是:
0 -> 6 -> 11 -> 7 -> 8
第一个setTimeout
🥂第一个setTimeout中有一个我们容易出错却又不怪我们的问题:
setTimeout(function() {
console.log(1);
async function async1() {
await async2()
console.log(2)
}
async function async2() {
console.log(3)
}
async1()
new Promise(function(resolve) {
console.log(4)
resolve()
}).then(function(){
console.log(5)
})
})
首先我们抛开setTimeout的外壳,执行顺序是:
1 -> 3 -> 4 -> 5 -> 2
这时候就引出了第一个争议点,因为你可以把这段代码拷到控制台,打印的结果却是:
1 -> 3 -> 4 -> 2 -> 5
造成这个结果的原因是Node8中的一个错误,然后后来,开发人员发现后面这种输出结果其实更符合我们的需要,毕竟这种结果更直观。所以他们开始尝试将这个特性代入正规规范中,也就有了"--harmony-await-optimization"。
而从从V8 v7.2和Chrome 72开始,这种优化已经成为正统。也就是说,虽然我们按照await的标准,当第二次执行到await且await后面跟着的是Promise时,await只是会resolve并放去微任务队列中,并在下一次微任务队列循环时才会再度执行await语句后面的语句。但我们要知道,这种标准最终屈服于await的性能优化。使得我们最终看到的都是:
1 -> 3 -> 4 -> 2 -> 5
第二个setTimeout
☕上面那段第一个setTimeout的分析,要知道我们是独立于这个函数来讲解的,那么和第二个setTimeout结合起来的话输出顺序又会是怎么样呢?🤷♂️
其实这个问题也可以转化为小问题去解决,我们现在纠结的无非是两个setTimeout是一个执行完再执行另一个呢?还是按照微任务队列的顺序交替执行呢?
其实这个答案我在第一部分就已经讲了,当我们微任务队列清空之后,主线程才会从宏任务队列里拿第一个setTimeout,而毫无疑问这个setTimeout执行过程中又会往微任务队列推新的Promise.resolve。同时我们又知道了如果JS主进程不清空微任务是不执行的。换句话说,只要主进程有东西,微任务队列也有东西,微任务队列的东西会一直执行直到空。
这部分刚刚贴的视频也有详细讲解。虽然有些人看我讲的就差不多懂了,但我还是推荐你去看一下这个视频。
讲到这儿,这个题的答案也就出来了🌺:
0 -> 6 -> 11 -> 7 -> 8 -> 1 -> 3 -> 4 -> 2 -> 5 -> 9 -> 10
理解若有错误欢迎指出,感谢😁