高级异步:并发控制与性能优化

16 阅读3分钟

上一期我们掌握了 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)重复请求直接秒返回★★★☆☆★★★★☆
弱网优化(骨架屏+延迟加载)感知速度提升明显★★☆☆☆★★★★☆
请求优先级 + 可取消核心数据优先,边缘可丢★★★★☆★★★☆☆

经典案例:商品详情页的“并行优化”

优化前(典型瀑布)

  1. 获取商品基本信息 → 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 做真正的后台并发计算

我们下一期见~
(最后一期:异步编程最佳实践与调试技巧 + 系列总结)

留言区互动:
你项目里最大的并发请求量是多少?
用过哪些并发控制方案?效果如何?