一道关于事件循环的代码题,希望不吝赐教~

题目:

const pA = () => {
  return new Promise(resolve => {
    resolve()
  })
}

const fn1 = async () => {
  fn2()
}
const fn2 = async () => {
  await pA()
  console.log('b')
}
async function fn() {
  await fn1()
  console.log('a')
}
fn() // b a

为什么输出是先b后a?

我是这么理解的【找bug】:
运行fn,检测到await,其后的代码会被添加到微任务队列,称作任务A,
fn的主代码块执行完毕,执行任务A,
执行fn1,执行fn2,发现了微任务pA()...,添加到微任务队列,称作任务B,
fn1的代码块执行完毕,发现没有返回值,便默认返回了Promise.resolve(),继续执行任务A代码块,【输出a】,
任务A执行完毕,执行任务B,【输出b】

但输出结果是先b后a,想了半天也说服不了自己~
希望各位不吝赐教,Thanks♪(・ω・)ノ

阅读 1.7k
3 个回答
const pA = () => {
  return new Promise(resolve => {
    resolve()
  })
}

const fn1 = () => {
  fn2()
    return  Promise.resolve()
}
const fn2 = () => {
  pA().then(() => console.log('b'))
  return Promise.resolve()
}
function fn() {
  fn1().then(() => console.log('a'))
}
fn() // b a

async/await不过是promise语法糖,你把await转为promise.then
比如

await fn1()
console.log('a')

=>

fn1().then(() => console.log('a'))

而且这个是层层嵌套的,如果fn1里也有await,那么同样把fn1替换为内部的promise.then。
await一旦用不好和回调地狱是一样的。它具有迷惑性。

在高人(呆子)的指点下想明白了,来自答一下,基于原理解的修改:
运行fn,检测到await,其后的代码会被添加到微任务队列,称作任务A,(此时还没运行到await的下一行,因此任务A并没有添加到任务队列)
执行fn1,执行fn2,执行pA(),发现了微任务pA().then,添加到微任务队列,称作任务B,
fn1的代码块执行完毕,发现没有返回值,便默认返回了Promise.resolve(),执行到输出a的一行,添加到微任务队列,称作任务A
主任务代码块执行完毕,从任务队列中取任务,先入先出,先b后a

从执行部分开始说起


fn()

fn 推入执行栈(Call Stack)


执行栈出栈 fn 并执行

async function fn() {

执行到此句时

  await fn1()

fn1 推入执行栈,但此时未执行到关键字 await


此时执行栈非空,继续对执行栈进行出栈操作,执行 fn1

const fn1 = () => {

执行到此句时

  fn2()

fn2 推入执行栈


此时执行栈非空,继续对执行栈进行出栈操作,执行 fn2

const fn2 = async () => {

执行到此句时

  await pA()

pA 推入执行栈,但此时未执行到关键字 await


此时执行栈非空,继续对执行栈进行出栈操作,执行 pA

const pA = () => {
  return new Promise(resolve => {
    resolve()
  })
}

pA 执行完毕,返回值为 Promise 对象
同时将 Promise 中的函数加入微任务(Microtask)队列(Queue)


上下文环境(Closure's Environment)切换至 fn2

await pA()

变成

await <Promise<undefined>>

await 关键字等待微任务执行


此时执行栈为空,因此开始执行微任务队列
微任务被执行

resolve => {
  resolve()
}

切换任务状态,并最终获得 undefined 结果


await 执行完毕,开始执行下一句

console.log('b');

控制台打印出 b


fn2 执行完毕,将 fn2 的返回结果是 Promise<void> 加入微任务队列


上下文环境切换至 fn1
fn1 执行完毕,将 fn1 的返回结果是 Promise<void> 加入微任务队列


上下文环境切换至 fn

await fn1()

变成

await <Promise<void>>

await 关键字等待微任务执行


因为微任务队列非空,因此开始从队列中逐一取出任务执行
按顺序先执行 fn2 返回的 Promise<void>
然后执行 fn1 返回的 Promise<void>,获得 undefined 结果
此时 await fn1() 完成


继续执行后续语句

console.log('a')

控制台打印出 a


fn 执行完毕,上下文环境切换至外层


外层所有需要执行的都执行完毕
(UI 部分不讨论)
检查执行栈是否为空,如果非空,则开始下一轮事件循环
如果一直为空,一段空闲时间后,回收内存

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题