有这样一道题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<script>
async function async1() {
console.log('async1 start');
let a = await async2()
console.log(a)
console.log('async1 end')
}
async function async2() {
console.log('async2')
return 'async2 return'
}
console.log('script start')
setTimeout(() => {
console.log('settimeout')
}, 0);
async1()
new Promise(function (resolve) {
console.log('promise1')
resolve()
}).then(function () {
console.log('promise2')
})
console.log('script end')
</script>

问打印顺序?

说实话,刚看到的时候我确实有点麻,这不是纯属为了出题而出题嘛,每次遇到这种题的时候,我都是拒绝的,实际业务又不会遇到。但是仔细研究了下,一道题让我稍微理解了eventloop promise async/await的执行方式,还是很值得的,在此记录如下。 

什么是宏任务和微任务

 我们都知道,js是一门单线程的语言,也就是说只有一条通道用来执行代码中的多个任务,代码在同步执行的时候遇到异步操作会放入队列里,等待主线程任务执行完后再从队列中拿出任务执行。队列里挂起的各种任务,就是宏任务(macrotask)和微任务(microtask)。宏任务和微任务分处于宏任务队列和微任务队列中。

除此之外,script,也就是最开始执行的同步代码,也是宏任务。 在浏览器里,常见的宏任务和微任务如下: 

宏任务:script,setTimeout/setInterval,postMessage,MessageChannel 

微任务:Promise 

执行机制

 宏任务和微任务具体是怎么执行的呢? 

 js主线程先执行同步代码(同步代码就是上文说的script,其实也是一个宏任务) 

执行过程中,遇到宏任务,放入宏任务队列,遇到微任务,放入微任务队列 

同步代码执行完后,从微任务队列中获取微任务放入主线程执行 

期间如果遇到微任务,将其放入微任务队列,再从微任务队列中获取任务执行 

当前为任务队列为空后,才从宏任务队列中获取任务执行 执行宏任务

重复以上步骤,就完成了eventloop,也就是常说的事件循环

promise和async/await

 ES6中javascript新增了promise对象,用于实现异步操作,promise意为承诺,我的理解是它包含了一个在未来才会返回值的事件(一般是异步操作),不管对错结果,它承诺会返回值。promise包含三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败) 并且状态只会根据异步操作的结果而改变。promise详解可以看阮一峰老师的promise教程

 在这里我们只需要知道,一个promise实例存在then方法和catch方法,而这两种方法都是微任务 

 async/await是es7新特性,如果说promise的出现是为了解决回调地狱,那async/await也是为了解决promise的这一问题,当项目中有大量互相依赖的异步请求的时候,就会出现大量的.then,不仅可读性差,也影响代码优雅(hhh) 

async/await详解推荐https://es6.ruanyifeng.com/#docs/async 

咱们直接说重点 async/await能让异步操作看起来像是同步代码,并且async的本质其实是返回一个立即resolved的promise函数,await会阻塞后面的代码执行,但是他会先执行右边表达式,等有返回值了再返回 

题目答案

 根据以上描述,相信大家也能推出来了,答案就是: 

1
2
3
4
5
6
7
8
9
//script start
//async1 start
//async2
//promise1
//script end
//async2 return
//async1 end
//promise2
//settimeout

首先代码同步执行,打印script start 

遇到setTimeout,把它放入宏任务队列 

然后执行async1,打印async1 start 

遇到await,先执行右边表达式,打印async2

后面代码被阻塞,跳出回到同步代码,执行new Promise 

Promise构造函数中的代码其实是同步代码,打印promise1 

执行到.then发现是微任务,放入微任务队列 

同步执行下面代码,打印script end 

同步代码执行完毕,回到被await阻塞的代码 

打印async2d的返回值async2 return 

打印async1 end 代码执行完,从微任务队列中读取任务执行,也就是执行promise.then方法,打印promise2 

至此当前宏任务执行完毕,从当前宏任务队列中读取任务执行,开始下一个事件循环 打印settimeout