批量执行 Promise 并按指定数量分组串行运行

本文介绍如何将一组 promise 按固定批次(如每 2 个)分组,并确保前一批全部完成后再执行下一批,适用于 api 限流、资源节制等场景。

在实际开发中(尤其是调用后端接口时),我们常需避免并发请求过多导致服务端压力过大或触发限流。此时,简单使用 Promise.all(promises) 会一次性发起所有请求;而逐个 await 又效率过低。理想方案是:分批并发执行,批间串行等待——即每批运行 n 个 Promise,全部 resolve 后再启动下一批。

以下是一个简洁、可复用的实现:

async function runInBatches(promiseFactories, batchSize = 2) {
  for (let i = 0; i < promiseFactories.length; i += batchSize) {
    const batch = promiseFactories.slice(i, i + batchSize);
    // 批内并发执行,批间 await 阻塞
    await Promise.all(batch.map(fn => fn()));
    console.log(`✅ Batch [${i}–${Math.min(i + batchSize - 1, promiseFactories.length - 1)}] completed`);
  }
}

// 示例:模拟 6 个 axios 请求(注意:传入的是函数,而非已执行的 Promise)
const apiCalls = [
  () => axios.post('/api', { id: 1 }),
  () => axios.post('/api', { id: 2 }),
  () => axios.post('/api', { id: 3 }),
  () => axios.post('/api', { id: 4 }),
  () => axios.post('/api', { id: 5 }),
  () => axios.post('/api', { id: 6 })
];

// 每批并发 2 个请求,共 3 轮
await runInBatches(apiCalls, 2);

? 关键设计说明

  • ✅ 传入的是 Promise 工厂函数(() => axios.post(...)),而非已创建的 Promise,避免请求提前发起;
  • ✅ 使用 for 循环 + await Promise.all(...) 实现“批内并行、批间串行”;
  • ✅ slice() 安全处理末尾不足 batchSize 的情况(自动截断,无越界风险);
  • ⚠️ 若某批中任一 Promise reject,整个 Promise.all 将拒绝,需根据业务需求添加错误处理(如 Promise.allSettled 或 try/catch 包裹)。

? 进阶建议

  • 如需容错(单个失败不影响整批),可改用 Promise.allSettled;
  • 如需动态控制并发数(如基于令牌桶),可结合 p-limit 等工具库;
  • 在 React 或 Vue 中调用时,注意避免在渲染函数中直接 await,应封装为事件处理器或 useEffect 副作用。

该模式兼顾效率与可控性,是前端批量请求调度的经典实践。