如何避免请求队列中频繁API调用被限流?

闲人树珂 阅读 14

我正在做一个实时数据同步功能,需要连续发送大量POST请求到API,但总被服务器限流返回429。我尝试用队列加setTimeout控制频率,但实际测试发现请求还是挤在一起发送了,代码哪里有问题?


let queue = [];
const MAX_CONCURRENT = 3;
let processing = false;

function enqueueRequest(data) {
  queue.push(data);
  if (!processing) {
    processing = true;
    processQueue();
  }
}

async function processQueue() {
  while (queue.length > 0 && MAX_CONCURRENT > 0) {
    const item = queue.shift();
    await fetch('/api/submit', {method: 'POST', body: item})
      .then(() => setTimeout(processQueue, 1000)) // 每次请求间隔1秒
      .catch(err => queue.unshift(item)); // 失败重新入队
    MAX_CONCURRENT--;
  }
  processing = false;
}

按理说设置了1秒间隔和并发限制,但监控发现请求还是在0.5秒内发出了5个,服务器返回的X-RateLimit-Remaining直接归零。是不是队列处理逻辑有竞态条件?或者Promise链没串起来?

我来解答 赞 2 收藏
二维码
手机扫码查看
1 条解答
程序猿樱潼
你这个队列的问题出在 Promise 链没串对,而且 setTimeout(processQueue, 1000) 这种写法根本不会阻塞执行,只是异步调度了一下,导致多个 processQueue 实例并行跑起来了,完全破坏了并发控制。

最致命的是你在 await fetch().then(...) 里又调用了 setTimeout(processQueue),但 await 只等 fetch,不等那个 setTimeout 后续。结果就是第一个请求发完,还没过一秒,await 就结束了,继续下一轮 while 循环,瞬间把后面几个全发出去了。而且你还写了 MAX_CONCURRENT--,但它是个常量,每次减都没用,下一次调用又重新从3开始?这变量放的位置也不对。

还有,并发数控制不能靠 while 循环 + await 单个请求,得用 Promise.all 控制最大并发,同时每个请求之间要有真实延迟。

直接给你一个能跑的版本:

let queue = [];
let isProcessing = false;
const MAX_CONCURRENT = 3;
const MIN_INTERVAL = 1000; // 1秒间隔

async function enqueueRequest(data) {
queue.push(data);
if (!isProcessing) {
isProcessing = true;
await processQueue();
isProcessing = false;
}
}

async function processQueue() {
const activeRequests = [];

while (queue.length > 0) {
// 拿出最多 MAX_CONCURRENT 个任务
const batch = [];
for (let i = 0; i < MAX_CONCURRENT && queue.length > 0; i++) {
batch.push(queue.shift());
}

// 并发执行这一批
const requests = batch.map(data => retryFetch(data));
activeRequests.push(Promise.all(requests));

// 批次之间加延迟
await new Promise(resolve => setTimeout(resolve, MIN_INTERVAL));
}

// 等所有请求完成(包括重试)
await Promise.all(activeRequests);
}

async function retryFetch(data, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const res = await fetch('/api/submit', {
method: 'POST',
body: data
});

if (res.status === 429) {
const retryAfter = parseInt(res.headers.get('Retry-After') || '1000');
await new Promise(resolve => setTimeout(resolve, retryAfter));
continue;
}

if (res.ok) return res;

} catch (err) {
if (i === retries - 1) throw err;
}

// 指数退避
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
}
}


关键点:
- MAX_CONCURRENT 控制每轮并发数,不是拿全局变量去减
- 批次之间用 await new Promise(setTimeout) 真实等待
- 失败重试独立封装,别往队列头 unshift,容易死循环
- 注意处理服务器返回的 Retry-After 头,这是标准做法

你现在的问题本质是 JS 里面异步流程控制没理清,awaitsetTimeout 混用很容易踩坑。记住:await 只等 Promise,不等 callback 回调。
点赞 2
2026-02-10 21:02