logan70 / Blog

写博客的地方,觉得有用的给个Star支持一下~
81 stars 9 forks source link

执行机制 - 复杂异步嵌套逻辑分析 #35

Open logan70 opened 4 years ago

logan70 commented 4 years ago

复杂异步嵌套逻辑分析

Async/Await 在事件循环中的表现

在分析之前,有必要了解一下Async/Await在事件循环中的表现,先看如下代码。

async function async1() {
  console.log('a')
  await async2()
  console.log('b')
}
async function async2() {
  console.log('c')
}

async1()

new Promise((resolve) => {
  console.log('d')
  resolve()
}).then(() => {
  console.log('e')
})

不同chrome版本表现不同,有以下两种情况:

首先说明:最新ECMAScript规范下,第一种为正确表现,下面解释原因。

最新ECMAScript规范

最新ECMAScript规范中,await直接使用Promise.resolve()相同语义,也就是说,如果await后跟的是一个Promise,则直接返回Promise本身,如果不是,则使用Promise.resolve包裹后返回,上述代码执行过程可以简化理解为:

console.log('a')
new Promise(resolve => {
  console.log('c')
  resolve()
}).then(() => {
  console.log('b')
})
new Promise((resolve) => {
  console.log('d')
  resolve()
}).then(() => {
  console.log('e')
})

console.log('b')在第一轮事件循环时就加入微任务队列,然后console.log('e')才加入微任务队列,故b的打印顺序在先。

老版ECMAScript规范

await后不论是否为Promise,都会产生一个新的Promise,再将后面跟的内容resolve出去。

其实最初关于async/await的相关规范和上述最新规范中行为是一致的,但是中间有一段时间ECMA规范有一些变化,只不过最后又变了回来

根据老版规范,上述代码执行过程可以简化理解为:

console.log('a')
new Promise((resolve1) => {
  resolve1(new Promise(resolve2 => {
    console.log('c')
    resolve2()
  }))
}).then(() => {
  console.log('b')
})
new Promise((resolve) => {
  console.log('d')
  resolve()
}).then(() => {
  console.log('e')
})

由于resolve1内又resolve了一个Promise,所以在这里已经是异步任务了,而不是立即变为fulfilled的状态,所以console.log('b')并不是在第一轮事件循环中被加入微任务队列,而console.log('e')仍然是在第一轮事件循环中就被加入微任务队列,所以e先于b打印,最终打印顺序为a c d e b

更多详细探讨可参考这篇文章

复杂异步嵌套分析

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')
  1. 定义函数async1async2打印script start
  2. 执行setTimeout,回调交由Web API处理,Web API将其加入宏任务队列;
  3. 执行async1打印async1 start
  4. 执行async2打印async2,由于左边有await,将console.log('async1 end')放入微任务队列;
  5. 执行new Promise,同步执行传入构造函数的函数,打印promise1
  6. promise完成,将console.log('promise2')所在函数放入微任务队列;
  7. 打印script end,当前任务执行完毕;
  8. 检查微任务队列并依次取出执行,打印async1 end打印promise2
  9. 微任务队列为空,执行栈为空,检查宏任务队列,取出任务执行,打印setTimeout
  10. 执行完毕。

故打印顺序为:

hansenwangvip commented 4 years ago

深入到ECMA规范了,很强!