面试官:"说说 async/await 的原理。"
你:"就是语法糖。"
面试官:"展开讲讲。"
你:"......"
别急,今天我们把 async/await 拆成 4 块乐高: Promise → Generator → 自执行器 → async/await。拼完就再也不会卡壳。
一、前菜:Promise 的"链式地狱"
js
function fetchUser(id) {
return fetch(`/user/${id}`).then(r => r.json());
}
fetchUser(1)
.then(user => fetch(`/order/${user.lastOrder}`))
.then(order => fetch(`/detail/${order.id}`))
.then(console.log)
.catch(console.error);
缺点:横向金字塔、错误处理分散、变量作用域混乱。
需求:像同步那样写异步 ------ async/await 登场!
二、第一块积木:Promise
- async 函数 永远返回一个 Promise。
- await 只能等 Promise 结果。
js
async function demo() {
return 123; // 自动包一层 Promise.resolve(123)
}
demo().then(console.log); // 123
三、第二块积木:Generator 暂停/恢复
Generator 可以"停住函数、记住上下文、外部注入值",是 async/await 的底层发动机。
js
function* gen() {
const a = yield 1; // 停!把 1 交出去,等外部把结果塞回 a
const b = yield 2 + a;
return b;
}
const g = gen();
g.next(); // {value: 1, done: false}
g.next(10); // 把 10 塞回 a,继续到 yield 12,{value: 12}
g.next(100); // 把 100 塞回 b,{value: 100, done: true}
四、第三块积木:自执行器(co 库 20 行实现)
把 Generator 的 .next()
写成自动循环,就成了 co:
js
function co(gen) {
return new Promise((resolve, reject) => {
const g = gen();
function step(val) {
const { value, done } = g.next(val);
if (done) return resolve(value);
Promise.resolve(value).then(step, reject);
}
step();
});
}
使用:
js
co(function* () {
const user = yield fetchUser(1);
const order = yield fetch(`/order/${user.lastOrder}`).then(r => r.json());
return order;
}).then(console.log);
这就是 Generator + 自执行器 的"伪 async/await"。
五、第四块积木:官方 async/await ------ 语法糖合体
Babel 转译后,你的 async 函数 ≈ 被 co 包装后的 Generator:
源码
js
async function getDetail(id) {
const user = await fetchUser(id);
const order = await fetch(`/order/${user.lastOrder}`).then(r => r.json());
return order;
}
转译后(简化)
js
function getDetail(id) {
return co(function* () {
const user = yield fetchUser(id);
const order = yield fetch(`/order/${user.lastOrder}`).then(r => r.json());
return order;
});
}
六、错误处理三板斧
场景 | 写法 |
---|---|
try/catch 捕获 | try { await xxx } catch(e) {} |
并发错误 | Promise.allSettled([...]) |
超时兜底 | await Promise.race([fetch(), sleep(5000)]) |
七、并发与串行对比代码
js
// 串行:2 s + 2 s = 4 s
const a = await delay(2000);
const b = await delay(2000);
// 并发:max(2 s, 2 s) = 2 s
const [a, b] = await Promise.all([delay(2000), delay(2000)]);
八、手写 async/await 面试题
题目:用 Generator 实现 sleep 函数 + async/await 效果。
js
function sleep(ms) {
return new Promise(r => setTimeout(r, ms));
}
function* genSleep() {
console.time('sleep');
yield sleep(1000);
console.timeEnd('sleep'); // ≈ 1000 ms
}
co(genSleep);
九、思维导图总结
javascript
async/await
├─ 语法糖
├─ 基于 Promise
├─ 内部使用 Generator + 自执行器
└─ Babel 转译 ≈ co 库
🏁 结语
把 async/await 拆成 4 块乐高后,你会发现:
- 它并不神秘,只是 Promise + Generator + 自执行器 的优雅封装。
- 写代码时:串行/并发、错误、超时,套路固定。
- 面试回答时:先画模型,再给代码,稳拿 Offer!
下次面试官再问 async/await 原理,你可以自信地说:
"先生,我会拼乐高。"