promise.all直接并发全部请求易致oom或连接超限,应分批串行执行:按concurrency切片数组,每批内promise.all并发、批间await串行等待。

Promise.all 会一次性发起所有请求,根本压不住并发量
直接 Promise.all(files.map(parseFile)) 看似简洁,但实际会瞬间创建上万个待执行的 Promise,全部进入 microtask 队列。Node.js 的 event loop 来不及调度,内存暴涨,进程大概率 OOM;浏览器端则可能触发连接数限制(Chrome 默认同域 6 个 TCP 连接),大量请求卡在 pending 状态,报错 net::ERR_INSUFFICIENT_RESOURCES 或超时。
关键不是 Promise.all 本身的问题,而是它不提供节流能力——它只负责“等全部完成”,不管你怎么塞进去。
用 Promise.all + 分批 map 实现可控并发
核心思路:把大数组切片,每批最多 N 个任务,批内用 Promise.all 并发执行,批间串行等待。不需要引入额外库,纯原生 JS 即可落地。
- 定义并发数
concurrency = 10(根据 I/O 负载和内存调整,文件解析类 CPU+I/O 混合任务建议 5–20) - 用
Array.from({ length: Math.ceil(files.length / concurrency) })生成批次数 - 每批取
files.slice(i * concurrency, (i + 1) * concurrency),再.map(parseFile)后传给Promise.all - 用
await串行等待每批完成(避免嵌套then)
async function parseFilesWithConcurrency(files, concurrency = 10) {
const results = [];
for (let i = 0; i <h3>parseFile 函数必须返回 Promise,且错误不能吞掉</h3><p>如果 <code>parseFile</code> 是同步函数(比如直接调 <code>JSON.parse</code>),<code>Promise.all</code> 里它就立刻执行完毕,失去异步调度意义;如果是回调风格(如 <code>fs.readFile</code> 旧 API),没包装成 Promise 就会变成 <code>Promise.all([undefined, undefined...])</code>,结果全为 <code>undefined</code>。</p>
- 务必确保
parseFile返回一个真正异步的 Promise,例如用fs.promises.readFile或util.promisify(fs.readFile) - 不要在
parseFile内部try/catch吞掉错误——Promise.all遇到任一拒绝就立即 reject,这是你感知单个文件失败的唯一机会 - 若需容错(比如跳过坏文件),改用
Promise.allSettled,但注意它返回的是{ status: 'fulfilled' | 'rejected', value | reason }结构,需后处理
更健壮的做法:用 async-pool 或手写队列控制
上面分批法简单,但有缺陷:某一批里如果某个文件解析特别慢(比如 10s),整批都要等它,其他 9 个快的也卡住。真实场景中,用动态队列能更好压平延迟。
推荐两个轻量方案:
- 用
async-pool库:npm install async-pool,调用pool(10, files, parseFile),内部自动维护运行中 Promise 数量,空出坑位立刻补新任务 - 手写简易队列(适合不想加依赖):维护一个
running = 0计数器和queue = [],每次parseFile开始前running++,结束(无论成功失败)后running--并queue.shift()?.(),启动下一个
真正难的不是并发控制本身,而是 parseFile 里资源释放是否干净——比如每个文件解析后是否关闭了临时 stream、是否清除了大对象引用。这些不处理,哪怕并发数设为 1,跑几千个照样内存泄漏。










