前言:关于js的异步执行顺序,宏任务、微任务这些,或者async/await这些概念,相信大家都已经耳熟能详了。现在,我们从一道前端面试题入手,检查一下自己对这些概念理解的程度,用于自查。
一、下面,我们来看看这道面试题:
其实,这道题的关键,不仅是说出正确的打印顺序,更重要的是能否说清楚每一个步骤,为什么这样执行。(面试的时候,有时候,面试官会想知道你的解题思路的。)
async function async1(){
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2(){
console.log("async2");
}
console.log("script start");
setTimeout(function(){
console.log("setTimeout");
},0);
async1();
new Promise(function(resolve){
console.log("promise1");
resolve();
}).then(function(){
console.log("promise2");
});
console.log("script end");
以下答案,以游览器的eventloop机制为准的:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
如果回答出来了正确的顺序,恭喜你已经完全理解了这些概念;如果回答有些误差,也没有关系,可以和我一起再来复习一遍吧。
二、前置内容
第1部分:对于async await的理解
主要会讲解3点内容:
async做了一件什么事情?
带async关键字的函数,它使得你的函数的返回值必定是promise对象。
如果async关键字函数返回的不是promise,会自动用promise.resolve()包装;
如果async关键字函数显式地返回promise,那就以你返回的promise为准。
那我们从一个简单的例子,来看下async关键字函数和普通函数返回值的区别吧:
async function fn1(){
return 123
}
function fn2(){
return 123
}
console.log(fn1())
console.log(fn2())
Promise{:123}
123
可以看出来,async函数也没啥,它无非就是把return值包装了一下,其他就跟普通函数一样。
关于async关键字还有哪些要注意的?
await在等什么?
await等的是右侧【表达式】的结果
右侧如果是函数,那么函数的return值就是【表达式】
右侧如果是一个'hello'或者什么值,那表达式的结果就是'hello'
async function async1(){
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2(){
console.log('async2')
}
async1()
console.log('script start')
可能大家都知道await会让出线程,阻塞后面的代码,那么上面的例子中,'async2'和'script start'谁先打印呢?
是从左向右执行,一旦碰到await直接跳出,阻塞async2()的执行?
还是从右向左,先执行async2后,发现有await关键字,于是让出线程,阻塞代码呢?
实践的结论是,从右向左的。先打印async2,后打印的script start
(之所以一提,因为经常看到这样的说法,【一旦遇到await就立刻让出线程,阻塞后面的代码】。这样的说法,会让我误以为,await后面的那个函数,async2()也直接被阻塞呢。)
await等到之后,做了一件什么事情?
那么右侧表达式的结果,就是await要等的东西。
等到之后,对于await来说,分2个情况
如果不是promise,await会阻塞后面的代码,先执行async外面的同步代码,同步代码执行完,再回到async内部,把这个非promise的东西,作为await表达式的结果
如果它等到的是一个promise对象,await也会暂停async后面的代码,先执行async外面的同步代码,等着promise对象fulfilled,然后把resolve的参数作为await表达式的运算结果。
三、画图来看清宏任务、微任务的执行过程,来解答开篇的面试题
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("script start");
setTimeout(function() {
console.log("setTimeout");
}, 0);
async1();
new Promise(function(resolve) {
console.log("promise1");
resolve();
}).then(function() {
console.log("promise2");
});
console.log("script end");
先分享一个个人理解的宏任务和微任务的概念,在脑海中的宏任务和微任务如图所示:
也就是【宏任务】、【微任务】都是队列。
一段代码执行时,会先执行宏任务中的同步代码,
下面就以面试题为例子,分析这段代码的执行顺序。
每次宏任务和微任务发生变化时,画一个图来表示他们的变化。
直接打印同步代码console.log('script start')
//首先是2个函数声明,虽然有async关键字,但不是调用就不看,然后首先是打印同步代码
console.log('script start')
将setTimeout放入宏任务队列
默认所包裹的代码,其实可以理解为是第一个宏任务,所以这里是宏任务2
调用async1,打印同步代码console.log('async1 start')
看到带有async关键字的函数,它仅仅是把return值包装成了promise,其他并没有什么不同的地方。所以就很普通的打印console.log('async1 start')
分析一下await async2()
前文提过await,1.它先计算出右侧的结果;2.然后看到await后,中断async函数
目前,就直接打印console.log('async2')
被阻塞后,要执行async之外的代码
执行new Promise(),Promise构造函数是直接调用的同步代码,所以console.log('promise1')
代码运行到promise.then()
代码运行到promise.then(),发现这个是微任务,所以暂时不打印,只是推入当前宏任务的微任务队列中。
注意:这里只是把promise2/推入微任务队列,并没有执行。微任务会在当前宏任务的同步代码执行完毕,才会依次执行
打印同步代码console.log('script end')
没什么好说的。执行完这个同步代码后,【async外的代码】终于走了一遍
下面该回到await表达式那里,执行await Promise.resolve(undefined)了
回到async内部,执行await Promise.resolve(undefined)
对于await Promise.resolve(undefined)如何理解呢?
如果一个promise被传递给一个await操作符,await将等待Promise正常处理完成并返回其处理结果。
在这个例子中,就是Promise.resolve(undefined)正常处理完成,并返回其处理结果。那么await async2()就算是执行结束了。
目前这个promised状态是fulfilled,等其处理结果返回就可以执行await下面的代码了。
那何时能拿到处理结果呢?
平时我们用promise,调用resolve后,何时能拿到处理结果?是不是需要在then的第一个参数里,才能拿到结果。
所以,这里的await Promise.resolve()就类似于
Promise.resolve(undefined).then((undefined)=>{
})
把then的第一个回调参数(undefined)=>{}推入微任务队列。
then执行完,才是await async2()执行结束。
await async2()执行结束,才能继续执行后面的代码
如图
此时,当前宏任务1都执行完了,要处理微任务队列里的代码。
微任务队列,先进先出的原则:
但是,微任务2执行后,await async2()语句结束,后面的代码不再被阻塞,所以打印
console.log('async1 end')
宏任务1执行完成后,执行宏任务2
宏任务2的执行比较简单,就是打印
console.log('setTimeout')。
参考博客:
8张图帮你一步步看清 async/await 和 promise 的执行顺序 https://segmentfault.com/a/1190000017224799
理解 JavaScript 的 async/await https://segmentfault.com/a/1190000007535316
【编程】充分理解JS async/await Promise 及 运行顺序 https://www.bilibili.com/read/cv4058649/