从Promise链到Async Function:azu/promises-book中的异步编程演进
引言
在现代JavaScript开发中,异步编程是每个开发者必须掌握的核心技能。azu/promises-book项目深入探讨了Promise和Async Function的使用方法,为我们展示了从传统Promise链到现代Async/Await的演进过程。本文将重点分析如何将基于Promise的链式调用转换为更简洁的Async Function写法。
模拟异步请求:dummyFetch函数
在讲解转换过程前,我们先建立一个基础的异步请求模拟函数:
function dummyFetch(path) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (path.startsWith("/resource")) {
resolve({ body: `Response body of ${path}` });
} else {
reject(new Error("NOT FOUND"));
}
}, 1000 * Math.random());
});
}
这个dummyFetch
函数模拟了网络请求的异步特性:
- 随机延迟(1000ms内)
- 成功路径(以/resource开头的路径)
- 失败路径(其他路径)
Promise链式调用处理数组资源
传统上,我们使用Promise链来处理多个异步请求:
function fetchResources(resources) {
const results = [];
return resources.reduce((promise, resource) => {
return promise.then(() => {
return dummyFetch(resource).then((response) => {
results.push(response.body);
return results;
});
});
}, Promise.resolve());
}
这种写法虽然可行,但存在几个问题:
- 嵌套层级深,可读性差
- 错误处理复杂
- 需要手动维护结果数组
使用Async Function重构
使用Async/Await语法可以大幅简化代码:
async function fetchResources(resources) {
const results = [];
for (const resource of resources) {
const response = await dummyFetch(resource);
results.push(response.body);
}
return results;
}
改进点:
- 代码呈现线性结构,更易理解
- 错误处理可以使用try/catch
- 不需要手动管理Promise链
常见陷阱与注意事项
1. forEach中的await陷阱
许多开发者会尝试在forEach中使用await:
// 错误示例
async function fetchResources(resources) {
const results = [];
resources.forEach(async (resource) => {
const response = await dummyFetch(resource);
results.push(response.body);
});
return results; // 总是返回空数组
}
问题原因:
- forEach不会等待异步回调完成
- 函数会在所有await完成前返回
解决方案:
- 使用for...of循环
- 或使用map+Promise.all
2. await的作用域限制
await只能在Async Function内部使用:
function main() {
await Promise.resolve(); // SyntaxError
}
这是JavaScript的故意设计,防止误用导致整个程序阻塞。
进阶技巧:结合Promise.all优化
当请求顺序不重要时,可以使用Promise.all并行处理:
async function fetchResources(resources) {
const responses = await Promise.all(
resources.map(resource => dummyFetch(resource))
);
return responses.map(response => response.body);
}
优势:
- 所有请求并行发送
- 总时间取决于最慢的请求
- 代码更简洁
性能考量
选择串行还是并行取决于业务需求:
-
串行请求(for...of + await):
- 保证执行顺序
- 适用于有依赖关系的请求
- 总时间 = 各请求时间之和
-
并行请求(Promise.all):
- 最大化利用网络带宽
- 适用于独立请求
- 总时间 ≈ 最慢的请求时间
错误处理比较
Promise链和Async/Await的错误处理方式对比:
// Promise链
fetchResources(resources)
.then(handleSuccess)
.catch(handleError);
// Async/Await
try {
const results = await fetchResources(resources);
handleSuccess(results);
} catch (error) {
handleError(error);
}
Async/Await的优势在于可以将异步错误用同步方式处理。
结论
azu/promises-book项目展示了JavaScript异步编程的演进:
- 从回调地狱到Promise链
- 从Promise链到Async/Await
- 结合Promise API与Async/Await的最佳实践
关键要点:
- Async/Await提供了更直观的异步代码写法
- 理解底层Promise机制仍然重要
- 根据场景选择合适的处理模式(串行/并行)
- 注意常见陷阱,特别是循环中的await使用
通过掌握这些技术,开发者可以编写出既简洁又高效的异步JavaScript代码。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考