从 Promise 到 async/await 需要几步?

28 阅读2分钟

为什么要写这篇文章?

因为我发现在做关于异步的面试题,遇到 async/await 就老是做错,明明我已经很清楚的记得那些是宏任务、那些是微任务。也记得 event loop的执行顺序

  • 一开始整个脚本作为一个宏任务执行

  • 执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列

  • 当前宏任务执行完出队,检查微任务列表,有则依次执行,直到全部执行完

  • 执行浏览器UI线程的渲染工作

  • 检查是否有Web Worker任务,有则执行

  • 执行完本轮的宏任务,回到2,依此循环,直到宏任务和微任务队列都为空

微任务包括: MutationObserverPromise.then()或catch()Promise为基础开发的其它技术,比如fetch APIV8的垃圾回收过程、Node独有的process.nextTick

宏任务包括scriptsetTimeoutsetIntervalsetImmediateI/OUI rendering

下面我们以一个 Promise 的题目展开

  • 第一题:
const promise = new Promise((resolve, reject) => { //1
  console.log(1); // 2
  resolve('success') // 3
  console.log(2); // 4
});
promise.then(() => {
  console.log(3); // 5
});
console.log(4); // 6

解析:

  • 第一行 new Promise (executor ) , 其中的 executor 会被立刻执行
  • 第二行 输出 1
  • 第三行 改变 promise 的 PromiseState:fulfilled, PromiseResult:success
  • 第四行 输出 2
  • 第五行 此时 promise 的 PromiseState 已经为 fulfilled,把console.log(3)放入微任务列表,执行
  • 第六行 输出 4
  • 此时当前宏任务(源于脚本)执行完成,检查微任务列表,执行console.log(3)
  • 输出 3

最初的 “pending” promise 相反,一个 resolved 或 rejected 的 promise 都会被称为 “settled”。 在第三步中,如果 new promise 的 executor 没有 resolve 或者 reject 函数,那么将一直不会执行第五步,把 console.log(3) 放入微任务列表

  • 第二题:
function fn1() {
  console.log("1");  //1
  new Promise(resolve => {
    console.log("2")  // 2
    resolve() // 3
  }).then(res => console.log("3")) // 4
}

fn1();  //5
console.log("4")  //6

解析:

  • 代码执行直接跳到第5行, 执行 fn1() 函数
  • 第一行 输出 1
  • 第二行 输出 2
  • 第三行 改变 promise 的 PromiseState:fulfilled, PromiseResult:undefined
  • 第四行 此时 promise 的 PromiseState 已经为 fulfilled,把console.log(3)放入微任务列表,执行
  • 第六行 输出 4
  • 最后执行微任务队列中的 console.log(3) 输出 3

答案: 1 2 4 3

那么我们如何把第二题变成 async/await 的形式呢?

async function fn1() {
  console.log("1");
  await async2();
  console.log("3");
}
async function async2() {
  console.log("2");
}
async1();
console.log("4")

上面转换后的代码和第二题的输出完全一样

结论: await 是重新把一个 newPromiseStatefulfilledpromise ,然后再把 await 后面的代码放入这个 promise.then() 中 ,这也是为什么 2 会在 4 之前输出

new Promise(resolve=>{console.log("2"); resolve()}),

这里其实也可以直接转变为

Promise.resolve(console.log("2"))