angqiuli 2020-02-16
JavaScript运行在浏览器中。浏览器是多线程的,但只分配了其中一条给JavaScript,作为它的主线程。对于编码者来说,JavaScript是单线程的。因此JavaScript中存在以下几种异步行为:
其中WEB API会执行类似setTimeout的倒数或发送AJAX请求这样的操作,而得到的返回值则会加入Event Queue中等待主栈为空时压栈执行。
练习题目
// 以下的程序将会按照如何的顺序执行? async function async1 () { console.log('async1 start') await async2() console.log('async1 end') } async function async2 () { console.log('async2') } console.log('script start') setTimeout(() => { console.log('setTimeout') }, 0) async1() new Promise((resolve) => { console.log('promise1') resolve() }).then(() => { console.log('promise2') }) console.log('script end')
因此这题的难点就是在async语句的嵌套中。首先,根据前面的分析,因为前面都是函数声明,不会输出内容,代码会先输出script start,然后setTimeout加入宏任务队列。接下来进入异步函数async1,我们知道,异步函数本质上是generator的语法糖,因此异步函数会一直同步地执行到await语句执行完毕,并且把下面的代码放到微任务队列中,因此代码执行到此还会依次输出async1 start和async2。紧接着创建Promise对象时executor被执行,输出promise1,并把then放入微任务中,最后输出script end。理论上说,到此JS主线程已经执行完毕,也就是主栈中是空的,接下来就要分别看微任务和宏任务队列了。看微任务队列中现在有两个Task,按刚才所说的顺序是先是async1 end,再是promise2,但此处V8引擎版本不同执行情况不同,有的版本是按照队列顺序执行,有的是先执行Promise再执行await后的语句(但新版本的V8引擎是属于先执行Promise这种情况的,因此会输出promise2和async1 end)。这种现象可能是由于底层实现中await转化为promise语句是放在加入队列前还是加入队列后执行的关系,但无论如何它们两个微任务都必会在宏任务前执行,因此最后会输出宏任务的setTimeout。
异步编程需要比较细地掌握它们三个微任务: