如何限制并发请求的数量避免浏览器卡死?

一淑涵 阅读 4

我有个页面要批量上传几十个文件,直接用 Promise.all 发起请求,结果浏览器直接卡住了,甚至报错“Too many connections”。

试过自己写了个队列控制,但逻辑有点乱,比如下面这样:

async function uploadWithLimit(files, limit = 3) {
  const results = [];
  for (let i = 0; i < files.length; i += limit) {
    const chunk = files.slice(i, i + limit);
    const promises = chunk.map(file => fetch('/upload', { method: 'POST', body: file }));
    results.push(...await Promise.all(promises));
  }
  return results;
}

但这样还是会在每批内并发太多,而且不能动态调度空闲的“槽位”。有没有更优雅的并发控制方案?

我来解答 赞 3 收藏
二维码
手机扫码查看
1 条解答
小金宇
小金宇 Lv1
你这段代码的问题在于它是"分批发送"而不是"滑动窗口",每批必须全部结束才能开始下一批,效率不高。建议改成用 Promise.race 配合 Set 来实现一个真正的并发池。

核心思路是:始终维护一个正在执行的 Promise 集合,一旦有请求完成,立刻补上下一个,这样永远保持最大并发数,不会出现空窗期。

async function limitConcurrency(tasks, limit) {
const results = [];
const executing = new Set();

for (const [index, task] of tasks.entries()) {
// 并发数达到上限时,等待任意一个完成
if (executing.size >= limit) {
await Promise.race(executing);
}

const p = Promise.resolve()
.then(() => task())
.then(result => {
results[index] = result;
})
.finally(() => executing.delete(p));

executing.add(p);
}

// 等待剩余任务全部完成
await Promise.all(executing);
return results;
}


然后你的上传函数就变得很干净了:

async function uploadWithLimit(files, limit = 3) {
const tasks = files.map(file => () =>
fetch('/upload', { method: 'POST', body: file })
);
return limitConcurrency(tasks, limit);
}


这里有几个关键点需要说明一下。

Set 存储正在执行的 Promise,配合 finally 自动删除已完成的任务,这样 Promise.race 每次都能正确感知到"有空位了"。

任务要包装成函数 () => fetch(...) 而不是直接传 Promise,否则循环的时候所有请求就已经发出去了,根本控制不住。

结果用索引赋值 results[index] = result,这样即使任务完成顺序乱掉,最终结果顺序还是跟输入保持一致。

这种写法比你自己写的 for 循环分批要优雅很多,而且能最大化利用并发槽位,不会出现"等最慢的那个"的情况。
点赞
2026-03-03 01:05