liangbus / blogging

Blog to go
10 stars 0 forks source link

关于 async/await 的执行时机 #20

Open liangbus opened 4 years ago

liangbus commented 4 years ago

关于事件循环的知识点,这里就不重复讲了,本文仅讨论 async/await 的执行时机,因为最近看到有一些事件循环的例子,里面有 async/await,发现自己对这方面并不熟悉,于是就自己琢磨了一下下

直接就从一些🌰讲起吧

setTimeout(function () {
  console.log(1)
}, 0)
console.log(2)
async function async1() {
  console.log(3)
  await async2()
  console.log(4)
}
async function async2() {
  console.log(5)
}
async1()
console.log(6)

执行下上述代码,结果为

> 2, 3, 5, 6, 4, 1

这里对4这个输出有点迷惑 这里执行到 await 的时候,跑进 async 执行完毕之后,会跳出 async 外执行,执行完外面的代码之后,再执行 await 后面的代码,然后再执行 setTimeout 中的代码 从结果导向,我以为这是同一轮循环,就是主线程(第一轮宏事件)

再看下面的例子:

setTimeout(function () {
  console.log(1)
}, 0)
Promise.resolve().then(() => {
  console.log(9)
})
console.log(2)
async function async1() {
  console.log(3)
  await async2()
  console.log(4)
}
async function async2() {
  console.log(5)
}
async1()
console.log(6)
Promise.resolve().then(() => {
    console.log(7)
})
setTimeout(() => {
    console.log(8)
}, 0)

在头尾,我均增加了一个微任务 结果为

> 2, 3, 5, 6, 9, 4, 7, 1, 8

从这里看起来是这样的(瞎猜) 第一轮宏循环:2,3,5,6 第一轮微循环:9 第二轮宏循环:4 第二轮微循环:7 第三轮宏循环:1,8

为什么 setTimeout 会最后才执行,明明最开始就加入宏队列中去了啊,Y ??? 此处一脸懵逼?

后经网上查阅相关资料,就发现了一些之前没有理解到位比较关键的点 async 方法返回的是一个 Promise 对象,如果在函数中 return 一个基本类型的变量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象,比如这样

async function f(x){
    return x + 1
}
f(1)
// Promise {<resolved>: 2}

await 又是在等什么? await 后面紧接的是一个表达式

[return_value] = await expression;

如果 await 后面跟的不是一个 Promise,那 await 后面表达式的运算结果就是它等到的东西;如果 await 后面跟的是一个 Promise 对象,await 它会“阻塞”后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值作为 await 表达式的运算结果。 等待 Promise 对象的 resolve 值,然后作为结果给到 await(从右到左执行) 平常我们是怎么获取 resolve 传进来的值呢? 通过 then 方法对吧 所以 await 关键字会等 async 真正的执行完,而 async 真正执行完是将其值返回,类似下面的执行语句

Promise.resolve(undefined).then((undefined) => { })

看到这里,发现它最后需要执行一个 then 方法,then 方法是微事件,所以这时候再重新看下上面的例子(增加了一些宏/微事件)

setTimeout(function () {
  console.log(1)
}, 0)
Promise.resolve().then(() => {
  console.log(9)
})
console.log(2)
async function async1() {
  console.log(3)
  await async2()
  console.log(4)
  new Promise(function (resolve) {
      console.log(11)
      resolve()
    }).then(function () {
      console.log(12)
    })
}
async function async2() {
  console.log(5)
}
async1()
console.log(6)
new Promise(function (resolve) {
  console.log(10)
  resolve()
}).then(function () {
  console.log(7)
})
setTimeout(() => {
    console.log(8)
}, 0)

来,一步步分析 先来声明两个队列

第一轮

微事件: 宏事件:

遇到第一个 setTimeout,加入 宏事件队列

第一轮

微事件: 宏事件:log(1)

遇到 Promise.resolve.then 加入微事件队列

第一轮

微事件:log(9) 宏事件:log(1)

执行主线程 log(2) 遇到声明函数,跳过~ 遇到函数 async1 执行,log(3) 遇到 await 并且右等式为 async 函数,进入并执行,log(5) 执行完 async2 函数体,由于没有返回值,会自动包装一个 Promise.resolve(undefined) 返回,由于要获取其返回值,需要执行 then 方法,因此加入微事件

第一轮

微事件:log(9),Promise.resolve(undefined).then() 宏事件:log(1)

因为 await 右边表达式没有执行完成,故继续等待(阻塞后续代码),然后此时跳出 async 函数,执行外部代码,log(6) 遇到 Promise 构造函数,立即执行,log(10) resolve 后把 then 方法加入微事件

第一轮

微事件:log(9),Promise.resolve(undefined).then(),log(7) 宏事件:log(1)

遇到 setTimeout 加入宏事件

第一轮

微事件:log(9),Promise.resolve(undefined).then(),log(7) 宏事件:log(1),log(8)

此时主线程执行完了,可以执行下一轮微事件了 log(9) Promise.resolve(undefined).then 执行完这句之后,右表达式真正结束了,await 获得了返回值

此时继续执行 await 后面的代码,log(4)

附注:这里经测试发现,微事件队列中,即使 Promise.resolve(undefined).then 执行完,本轮队列后续仍有待执行任务,此时也会中断队列任务的执行,返回原主线程继续执行相关代码

遇到 Promise 构造函数,log(11),then 继续加入微事件队列

第一轮

微事件:log(7),log(12) 宏事件:log(1),log(8) 已执行过 log(9),Promise.resolve(undefined).then(),

这时 await 后代码执行完毕,回到刚刚微事件队列继续执行 log(7), log(12) 这一轮微事件已经结束,执行下一轮的宏事件

第一轮

微事件: 宏事件:log(1),log(8)

log(1) log(8)

最终结果

2, 3, 5, 6, 10, 9, 4, 11, 7, 12, 1, 8

参考: async 函数 -- 阮一峰 8张图让你一步步看清 async/await 和 promise 的执行顺序