第 28 题:手写 async/await(Generator 自动执行器原理)

好的,继续 第 34 题:手写 async/await(Generator 自动执行器原理)

这是高级工程师面试的重点,常用于考察你对 async/await 的底层理解。


一、为什么要手写 async/await?

面试官喜欢让你证明:

  • 你理解 async/await 是 Promise + Generator 的语法糖
  • 你知道 await 是如何"暂停"函数的
  • 你能写一个自动执行器(类似 TJ 的 co 库)

所以我们需要实现:

✔ 自动迭代 Generator

✔ 遇到 Promise 自动等待

✔ yield 结果继续向下执行

这就模拟了 async/await。


二、目标:把 Generator 变成 async 函数

假设一个 Generator:

javascript 复制代码
function* gen() {
  const a = yield Promise.resolve(1);
  const b = yield Promise.resolve(a + 1);
  return b + 1;
}

我们希望让它这样运行:

scss 复制代码
run(gen).then(console.log); // 输出 3

三、手写一个 async/await 的核心代码(简化版)

下面这段代码是面试够用的核心:

vbnet 复制代码
function run(genFn) {
  return new Promise((resolve, reject) => {
    const gen = genFn();

    function step(nextF, arg) {
      let next;
      try {
        next = nextF.call(gen, arg);
      } catch (e) {
        return reject(e);
      }

      if (next.done) {
        return resolve(next.value);
      }

      Promise.resolve(next.value).then(
        v => step(gen.next, v),
        e => step(gen.throw, e)
      );
    }

    step(gen.next);
  });
}

四、它如何实现 async/await?(逐行解释)

1. 启动 Generator

ini 复制代码
const gen = genFn();

2. 定义 step,用于迭代 Generator

javascript 复制代码
function step(nextF, arg) { ... }

3. 调用 next / throw:

ini 复制代码
next = nextF.call(gen, arg);

4. 如果 done === true → 表示 return 出来了 → resolve

lua 复制代码
if (next.done) return resolve(next.value);

5. 如果 yield 得到一个 Promise

javascript 复制代码
Promise.resolve(next.value)

6. Promise 成功 → 调用 next 继续执行

ini 复制代码
v => step(gen.next, v)

7. Promise 失败 → 调用 throw 抛回 Generator

ini 复制代码
e => step(gen.throw, e)

五、这段代码等价于 async/await 的机制

比如 async 写法:

csharp 复制代码
async function test() {
  const a = await foo();
  return a + 1;
}

等价于 Generator 写法:

javascript 复制代码
function* test() {
  const a = yield foo();
  return a + 1;
}

等价于调用自动执行器:

scss 复制代码
run(test);

这就是 async/await 的本质!


六、面试官常问的补充点

1. 为什么要用 Promise.resolve 包装?

因为 yield 后面可能不是 Promise,也要兼容。


2. async/await 为什么会更慢?

  • 语法糖 → 额外包装 Promise
  • 微任务更多
  • 不能并行,需要串行等待

3. 怎么让多个 await 并行?

错误方式(串行):

arduino 复制代码
await a();
await b();

正确方式(并行):

ini 复制代码
await Promise.all([a(), b()]);

七、20 秒面试背诵版

async/await 本质是 Generator 的语法糖。

await = yield + 自动执行器。

我们可以通过写一个 co 风格的执行器来模拟 async/await:

即不断调用 next(),遇到 Promise 等待完成后继续执行,直到 done=true 为止。


如果你愿意,我可以继续 第 35 题:模块化原理(CommonJS vs ESModule)

这也是前端必考。

相关推荐
前端一课1 小时前
第 33 题:浏览器渲染流程(Reflow 重排、Repaint 重绘、Composite 合成)*
前端·面试
前端一课1 小时前
前端面试第 34 题:事件循环(Event Loop)—— 必考高频题
前端·面试
前端一课1 小时前
第 26 题:Vue2 和 Vue3 的响应式原理有什么区别?为什么 Vue3 要用 Proxy 替代 defineProperty?
前端·面试
前端一课1 小时前
第 30 题:模块化原理(CommonJS vs ESModule)
前端·面试
前端一课1 小时前
第 31 题:Tree Shaking 原理与常见失效原因(高频 + 难点 + 面试必考)
前端·面试
前端一课1 小时前
第 27 题:Promise 实现原理(含手写 Promise)
前端·面试
前端一课1 小时前
第 32 题:深入理解事件循环(Event Loop)、微任务、宏任务(详细 + 难点 + 易错点)
前端·面试
前端一课2 小时前
【前端每天一题】🔥 第 25 题:什么是 Virtual DOM?它的优缺点是什么?Diff 算法是如何工作的?
前端·面试
前端一课2 小时前
【前端每天一题】第 23 题:闭包(Closure)与作用域链(详细 + 面试模板 + 速记卡)
前端·面试