在上一篇文章中,我们已经完成了数据准备工作。本篇将重点介绍一个在真实业务中几乎绕不开的问题:
当需要对一组数据逐个请求接口时,如何优雅、安全地进行批量请求?
如果处理不当,轻则页面卡顿,重则接口被限流甚至封禁。
一、为什么不能直接 Promise.all?
最常见的写法是这样:
ini
Promise.all(
list.map(item => request(item))
);
这个写法的问题在于:
-
并发数量 不可控
-
列表一长就会:
- 打满浏览器并发
- 压垮后端服务
-
一旦某个请求失败,整体直接 reject
在区县、设备、用户、文件等数量不确定的场景下,这种方式风险极高。
二、我们真正需要的是什么?
一个成熟的「批量请求」方案,至少应该满足:
- 限制并发数量
- 支持批次间隔(节流)
- 单个失败不影响整体流程
- 能够感知整体完成状态
- 结果和错误可以分开统计
三、核心设计思路
1️⃣ 把"请求"和"执行"解耦
- 批量工具只负责 调度
- 具体请求由调用方传入
javascript
(task) => Promise
2️⃣ 固定并发数,分批执行
- 每一批最多 N 个任务
- 当前批次完成后,延迟一段时间再执行下一批
3️⃣ 所有任务最终返回一个统一结果
ini
{
results: [],
errors: []
}
四、批量请求使用方式(示例)
下面是一个典型的使用方式:
javascript
this.batchRequestWithLimit(
taskList,
(task) => {
return apiRequest(task);
},
(task, data) => {
console.log(`任务 ${task} 成功`, data);
},
5, // 最大并发数
200, // 批次间隔(ms)
)
.then(({ results, errors }) => {
console.log(
`批量请求完成:成功 ${results.length} 个,失败 ${errors.length} 个`
);
})
.catch(err => {
console.error("批量请求异常:", err);
});
从使用者视角看,这个方法具备:
- 调用简单
- 参数语义清晰
- 结果可控
五、执行流程拆解(非常关键)
整个批量请求的生命周期可以拆成 5 步:
Step 1:任务队列初始化
ini
const queue = [...taskList];
Step 2:取出当前批次
ini
const batch = queue.splice(0, limit);
Step 3:并发执行当前批次
ini
await Promise.allSettled(
batch.map(task => executor(task))
);
Step 4:等待节流时间
scss
await sleep(interval);
Step 5:继续下一批,直到队列清空
六、为什么 Promise.allSettled 是关键
相比 Promise.all:
| 方法 | 任一失败 | 结果可控 |
|---|---|---|
| Promise.all | ❌ 整体失败 | ❌ |
| Promise.allSettled | ✅ 单独失败 | ✅ |
在批量请求场景中:
失败是常态,而不是异常
我们要做的是:
- 接受失败
- 记录失败
- 不中断流程
七、这种模式适合哪些场景?
这种批量请求模型,适用于:
- 区县 / 城市 / 设备 / 用户 批量拉取
- 文件批量上传 / 下载
- 批量校验、扫描、统计
- 任何「数量不确定 + 接口有压力」的业务场景
八、总结一句话
批量请求不是"多发请求",而是"有节制地并发执行任务"。
通过:
- 限制并发
- 分批执行
- 错误隔离
- 统一收敛结果
你可以把一个"危险操作",变成一个可控、可扩展、可维护的工程能力。