一道有趣的 async await 练习题

30 阅读2分钟

请分析以下函数输出顺序

async function asy1() {
  console.log(1);
  await asy2();
  console.log(2);
}

async function asy2() {
  await setTimeout(() => {
    Promise.resolve().then(() => console.log(3));
  }, 0);
  console.log(4);
}

asy3 = async () => {
  Promise.resolve(6).then(() => console.log(6));
};

asy1();
console.log(7);
asy3();

代码看着比较复杂,我们可以试着简化下代码,先把 asy2 简化下,把 async 改写成普通的 Promise 链式调用,看起来更清晰, 首先 setTimeout 会返回一个定时器 id,是个 number 类型,那其实就等价于 await 1 根据 js 标准文档 developer.mozilla.org/zh-CN/docs/…

async function foo() {
  await 1;
}

等价于

function foo() {
  return Promise.resolve(1).then(() => undefined);
}

因此 asy2 可以改写成

function asy2() {
  setTimeout(() => {
    Promise.resolve().then(() => console.log(3));
  }, 0);

  return Promise.resolve(1)
    .then(function () {
      console.log(4);
    });
}

接下来把 asy1 也改写成普通的 Promise 链式调用

function asy1() {
  console.log(1);
  asy2().then(() => {
    console.log(2);
  });
}

来看下改写后的完整代码

function asy1() {
  console.log(1);
  asy2().then(() => {
    console.log(2);
  });
}

async function asy2() {
  setTimeout(() => {
    Promise.resolve().then(() => console.log(3));
  }, 0);

  return Promise.resolve(1)
    .then(function () {
      console.log(4);
    });
}

asy3 = async () => {
  Promise.resolve(6).then(() => console.log(6));
};

asy1();
console.log(7);
asy3();

好像看着还是有点复杂,那我们试着把 asy1 和 asy2 合并下,并且去掉函数封装,直接执行,这样看着更加直观,最终代码如下

console.log(1);

setTimeout(() => {
  Promise.resolve().then(() => console.log(3));
}, 0);

Promise.resolve(1)
  .then(function () {
    console.log(4);
  })
  .then(() => {
    console.log(2);
  });

console.log(7);

Promise.resolve(6).then(() => console.log(6));

根据 js 事件循环机制,首先将同步代码放到执行栈执行,也就是先执行

console.log(1);
console.log(7);

输出顺序为 1 7

同时将 Promise.resolve().then(() => console.log(3)); 放到宏任务队列,接着往下看 运行到 Promise.resolve(1) ,将其加入微任务队列,我们注意到后面同时写了 2 个 then 回调,但是此时 js 引擎只会等 Promise.resolve(1) 执行完成后才会将 then 中的回调加入到微任务队列,举个例子

以下代码其运行顺序的伪代码为 1 2 3 4

Promise.resolve()
  .then(() => console.log(1))
  .then(() => console.log(3))
  
Promise.resolve()
  .then(() => console.log(2))
  .then(() => console.log(4))

所以对于

Promise.resolve(1)
  .then(function () {
    console.log(4);
  })
  .then(() => {
    console.log(2);
  });

console.log(7);

Promise.resolve(6).then(() => console.log(6));

可以很容易知道顺序为 7 4 6 2,根据js引擎会将当前微任务队列全部清空,再去执行一个宏任务的原则,我们可以知道最后输出顺序为 1 7 4 6 2 3