共计 2116 个字符,预计需要花费 6 分钟才能阅读完成。
异步编程的救星:async/await 全面指南
从回调地狱到曙光初现
- 回调地狱的泥潭
- 嵌套金字塔:典型的读取文件后处理数据再写入的场景,3 层回调就会形成向右延伸的三角形代码结构
- 错误处理分散:每个回调都要单独处理 err,导致重复代码遍布各处
-
流程控制困难:想实现简单的 ” 先 A 后 B 并行 C ” 逻辑,需要手动维护状态标志

-
Promise 的局限
- 链式调用虽然扁平化了代码,但 then() 链仍然存在视觉跳转
- 多个并行请求需要手动维护计数器或使用 Promise.all
- 错误捕获虽然可以通过 catch 统一处理,但依然需要返回 Promise 链
// 典型 Promise 链
fetchData()
.then(process)
.then(store)
.catch(logError);
async/await 实现原理
- 语法糖的真相
- 通过 Babel 编译可以看到,async 函数被转换为 Generator+ 自动执行器
- await 语句实际上会被拆解为 yield Promise
- V8 引擎会为 async 函数创建特殊的微任务调度逻辑
// 编译前后的代码对比
async function example() {const r = await fetch('/api');
return r.json();}
// 编译为类似:function example() {return spawn(function*() {const r = yield fetch('/api');
return r.json();});
}
两种范式对比
- 错误处理对比
- Promise 方案需要单独 catch 块或链式 catch
- async/await 可以用传统的 try/catch 结构
// Promise 方式
function fetchUser() {return fetch('/user')
.then(r => r.json())
.catch(e => {console.error('Failed:', e);
return {default: true};
});
}
// async/await 方式
async function fetchUser() {
try {const r = await fetch('/user');
return await r.json();} catch (e) {console.error('Failed:', e);
return {default: true};
}
}
高级应用技巧
- 并行优化方案
- 多个独立请求应该并行发起
- await 应该在所有 Promise 创建完成后使用
// 低效写法
const a = await getA();
const b = await getB();
// 优化写法
const [a, b] = await Promise.all([getA(), getB()]);
- 立即执行函数妙用
- 模块顶层可以使用 IIFE 突破 await 限制
- 适用于初始化场景
// 在模块顶层使用
(async () => {const config = await loadConfig();
initApp(config);
})();
常见陷阱防范
- 循环中的坑
- for 循环内 await 会导致顺序执行
- 应该先创建所有 Promise 再等待
// 错误示范
for (const url of urls) {const data = await fetch(url); // 串行!}
// 正确做法
const promises = urls.map(url => fetch(url));
const results = await Promise.all(promises);
- 未处理 rejection
- 忘记 catch 会导致静默失败
- 建议全局监听 unhandledRejection
process.on('unhandledRejection', (reason) => {console.error('未处理的 Promise:', reason);
});
性能优化实践
- V8 引擎优化
- Node.js 12+ 对 async/await 有专门优化
- 高频调用场景建议测试不同版本性能
- 避免在热路径中创建不必要的 Promise
// 低效写法
async function heavyTask() {return await compute(); // 多余的 Promise 包装
}
// 优化写法
async function heavyTask() {return compute(); // 直接返回 Promise
}
思考与实践
- 可取消异步任务
- 如何实现类似 axios 的 CancelToken?
- 提示:可以利用 Promise.race 和 AbortController
// 尝试实现以下接口
async function cancellableFetch(url, signal) {// 你的实现}
// 使用示例
const controller = new AbortController();
const promise = cancellableFetch('/api', controller.signal);
// 取消请求
controller.abort();
写在最后
经过这段时间在项目中的实践,async/await 确实大幅提升了异步代码的可读性。不过要注意它并不是银弹,在需要精细控制异步流程时,结合传统 Promise 方法可能会更合适。建议新项目可以优先采用 async/await,同时注意本文提到的各种边界情况处理。
正文完
发表至: 未分类
近两天内

