好的,继续 第 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) 。
这也是前端必考。