上一期我们掌握了 Fetch + async/await 的基本网络请求能力。
但真实项目中经常遇到下面这些场景:
- 需要同时请求 50~200 个接口
- 不能让全部请求同时发出(服务器会限流或直接封 IP)
- 请求顺序有依赖,但又想尽可能并行
- 防止瀑布式请求把页面加载时间拉长到几秒甚至十几秒
这一期我们就来系统解决这些“高级异步”问题。
1. 并发控制的核心思路
| 并发方式 | 最大同时请求数 | 适用场景 | 实现难度 |
|---|---|---|---|
| 全部同时发 | 无限制 | 接口少、服务器不限流 | ★☆☆☆☆ |
| Promise.all | 全部同时 | 数量少(<30个) | ★☆☆☆☆ |
| 固定并发数分批 | 3~10 | 大批量请求 + 服务器有限流 | ★★☆☆☆ |
| 令牌桶/滑动窗口 | 动态控制 | 严格限流、需要平滑流量 | ★★★★☆ |
| 带优先级 + 超时 | 动态 + 优先级 | 核心接口优先、边缘接口可丢弃 | ★★★★☆ |
2. 最常用的几种实现方式(代码示例)
方式1:简单粗暴版 - Promise.all + 分组
async function fetchAllInBatches(urls, batchSize = 6) {
const results = [];
for (let i = 0; i < urls.length; i += batchSize) {
const batch = urls.slice(i, i + batchSize);
const batchPromises = batch.map(url => fetch(url).then(r => r.json()));
const batchResults = await Promise.all(batchPromises);
results.push(...batchResults);
}
return results;
}
最常用、最好理解,适合绝大多数场景。
方式2:并发限制工具函数(推荐生产使用)
class ConcurrencyLimiter {
constructor(maxConcurrent) {
this.max = maxConcurrent;
this.running = 0;
this.queue = [];
}
async run(task) {
if (this.running >= this.max) {
await new Promise(resolve => this.queue.push(resolve));
}
this.running++;
try {
return await task();
} finally {
this.running--;
if (this.queue.length > 0) {
const next = this.queue.shift();
next();
}
}
}
}
// 使用示例
const limiter = new ConcurrencyLimiter(5); // 最多同时5个
const allResults = await Promise.all(
urls.map(url =>
limiter.run(() => fetch(url).then(r => r.json()))
)
);
方式3:p-limit / p-queue(社区最流行库)
// 使用 p-limit(非常推荐,体积小、API 优雅)
import pLimit from 'p-limit';
const limit = pLimit(4); // 最多4个并发
const results = await Promise.all(
items.map(item =>
limit(() => processItem(item))
)
);
目前(2026年)前端项目中最受欢迎的并发控制方案,几乎成为“标配”。
3. 性能优化实战技巧
| 优化手段 | 效果 | 实现成本 | 推荐指数 |
|---|---|---|---|
| 请求合并(BFF/GraphQL) | 1次请求代替 N 次 | ★★★☆☆ | ★★★★★ |
| 预请求 / prefetch | 用户还没点已经开始请求 | ★★☆☆☆ | ★★★★☆ |
| 瀑布 → 并行转换 | 串行 8s → 并行 2.5s | ★★☆☆☆ | ★★★★★ |
| 缓存(memory + IndexedDB) | 重复请求直接秒返回 | ★★★☆☆ | ★★★★☆ |
| 弱网优化(骨架屏+延迟加载) | 感知速度提升明显 | ★★☆☆☆ | ★★★★☆ |
| 请求优先级 + 可取消 | 核心数据优先,边缘可丢 | ★★★★☆ | ★★★☆☆ |
经典案例:商品详情页的“并行优化”
优化前(典型瀑布):
- 获取商品基本信息 → 2. 获取 SKU → 3. 获取推荐商品 → 4. 获取评论 → 5. 获取店铺信息
优化后:
async function loadProductPage(productId) {
const [basic, skus, comments, shop] = await Promise.all([
fetchBasic(productId),
fetchSkus(productId), // 可与 basic 并行
fetchComments(productId), // 可提前请求
fetchShopInfo(basic.shopId) // 依赖 basic,但可延迟
]);
const recommends = await fetchRecommends(basic.category); // 最后请求
return { basic, skus, comments, shop, recommends };
}
时间从 8~10秒 → 2.5~3.5秒(弱网环境下感知差距更大)
4. 小结与进阶路线
当前阶段你应该已经掌握:
- Promise.all / allSettled 的合理使用
- 固定并发数的实现方式(手写 + p-limit)
- 如何把瀑布式请求改造成并行
- 基本的请求超时与取消(AbortController)
下一阶段值得深入的方向:
- 更复杂的流量控制(令牌桶、漏桶)
- 请求优先级队列
- 自动重试 + 指数退避
- Service Worker + Cache API 的离线优化
- Web Workers 做真正的后台并发计算
我们下一期见~
(最后一期:异步编程最佳实践与调试技巧 + 系列总结)
留言区互动:
你项目里最大的并发请求量是多少?
用过哪些并发控制方案?效果如何?