使用Promise.all执行批量网络请求时内存不足的优化问询
嘿,我来帮你搞定这个批量请求的内存问题!你现在遇到的核心痛点是,当处理几万甚至更多ID时,一次性并发所有请求会把堆内存撑爆,对吧?结合你的纯JS需求,我整理了几个实用的优化方案,咱们一步步来看:
优化方案解析
1. 先修正当前分块逻辑的bug
你当前的分块代码存在逻辑漏洞,比如slice(lengthOfIds - 1000, lengthOfIds)和lengthOfIds -= 1001的写法很容易导致ID重复或漏切。正确的分块方式应该是从数组头部按固定大小切割,逻辑简单且不易出错:
function chunkArray(arr, chunkSize) { const chunks = []; let tempArr = [...arr]; // 克隆原数组,避免修改源数据 while (tempArr.length > 0) { chunks.push(tempArr.splice(0, chunkSize)); } return chunks; }
比如25000个ID按1000为一批,会被分成25个清晰的批次,完全不会出现重复或遗漏的问题。
2. 限制并发请求数量(关键优化)
你之前用Promise.all一次性发起所有批次的请求,这会导致所有请求的响应同时驻留在内存中,当批次数量过多时,堆内存很容易被占满。解决办法是限制同时运行的请求数量,比如每次只跑5-10个请求,完成一个再发起下一个,这样内存占用会大幅降低。
下面是纯JS实现的并发控制示例:
const axios = require('axios'); async function batchRequestWithConcurrency(ids, chunkSize = 1000, concurrency = 5) { // 先按指定大小分块 const chunks = chunkArray(ids, chunkSize); const results = []; // 维护一个正在运行的请求队列 const runningPromises = []; for (const chunk of chunks) { // 发起当前批次的请求(注意:不需要JSON.stringify,axios会自动序列化数组) const requestPromise = axios.post(url, { keys: chunk }) .then(response => { // 合并当前批次的结果 results.push(...response.data); // 从运行队列中移除已完成的请求 const index = runningPromises.indexOf(requestPromise); if (index !== -1) runningPromises.splice(index, 1); }) .catch(err => { // 错误处理:可以加重试逻辑或记录日志 console.error(`批次请求失败:`, err); const index = runningPromises.indexOf(requestPromise); if (index !== -1) runningPromises.splice(index, 1); }); runningPromises.push(requestPromise); // 如果并发数达到上限,等待其中一个请求完成再继续 if (runningPromises.length >= concurrency) { await Promise.race(runningPromises); } } // 等待所有剩余请求完成 await Promise.all(runningPromises); return results; }
这个方案的核心优势:
- 同时运行的请求数量被严格控制,不会瞬间占用大量内存
- 分块逻辑清晰,彻底避免原代码中的ID处理错误
- 错误处理更健壮,单个批次失败不会导致整个任务崩溃
3. 流式处理(进一步降低内存占用)
如果你的应用不需要一次性把所有数据都保存在内存中(比如可以边获取边写入数据库或做业务处理),可以把合并结果的逻辑换成直接处理当前批次的数据,这样内存里永远只存一个批次的内容,内存占用会降到最低:
async function batchRequestWithStreaming(ids, chunkSize = 1000, concurrency = 5) { const chunks = chunkArray(ids, chunkSize); const runningPromises = []; for (const chunk of chunks) { const requestPromise = axios.post(url, { keys: chunk }) .then(async response => { // 直接处理当前批次的数据,比如写入数据库 await processBatchData(response.data); // 替换成你的业务处理函数 const index = runningPromises.indexOf(requestPromise); if (index !== -1) runningPromises.splice(index, 1); }) .catch(err => { console.error(`批次请求失败:`, err); const index = runningPromises.indexOf(requestPromise); if (index !== -1) runningPromises.splice(index, 1); }); runningPromises.push(requestPromise); if (runningPromises.length >= concurrency) { await Promise.race(runningPromises); } } await Promise.all(runningPromises); }
这种方式特别适合处理十万级甚至更高数量的ID,内存占用几乎不会随ID数量增长而大幅上升。
4. 其他小优化
- 去掉不必要的JSON序列化:原代码中
keys: JSON.stringify(arr)完全没必要,axios会自动把JS对象序列化为JSON,后端直接接收数组即可,否则还要额外解析字符串,增加不必要的开销。 - 动态调整chunkSize:可以根据API的限制和内存情况灵活调整批次大小,比如内存紧张时设为500,API允许更大批次时设为2000。
- 增加重试机制:给失败的请求加重试逻辑(比如最多重试3次),避免因网络波动导致的请求失败。
总结
优先采用限制并发数+正确分块的方案,这能快速解决你当前的内存问题;如果后续ID数量持续增长到十万级甚至更高,再考虑流式处理的方案,进一步降低内存占用。
内容的提问来源于stack exchange,提问作者mralanlee
相关产品推荐
相关产品推荐

