Skip to content

前端并发请求控制

TIP

在前端处理大量请求时,控制并发是优化性能、避免资源竞争或服务器压力的关键手段。以下是几种常见的并发控制方案,从简单到复杂逐步递进。

1. 原生 Promise.all 的缺陷

默认情况下,Promise.all 会一次性发起所有请求,可能导致浏览器或服务器过载:

javascript
// 一次性并发所有请求(不推荐)
const requests = [fetch(url1), fetch(url2), ..., fetch(url50)];
Promise.all(requests).then(results => { ... });

2. 手动实现并发队列

通过队列机制控制同时进行的请求数量,例如每次最多同时发送 N 个请求。

实现代码示例

javascript
function controlledConcurrency(urls, maxConcurrent = 5) {
  let index = 0;
  const results = [];

  // 递归执行单个请求
  const runNext = async () => {
    if (index >= urls.length) return;
    const currentIndex = index++;
    try {
      const response = await fetch(urls[currentIndex]);
      results[currentIndex] = response;
    } catch (error) {
      results[currentIndex] = error;
    }
    await runNext(); // 递归启动下一个请求
  };

  // 初始化并发池
  const workers = Array(Math.min(maxConcurrent, urls.length))
    .fill(null)
    .map(runNext);

  return Promise.all(workers).then(() => results);
}

// 使用示例
const urls = [url1, url2, ..., url50];
controlledConcurrency(urls, 5).then(results => {
  console.log('所有请求完成:', results);
});

3. 使用第三方库(推荐)

利用现成的工具库简化代码,例如:

  • p-limit(轻量级并发控制)
  • async-pool(基于 Promise 的并发池)

使用 p-limit 示例

javascript
import pLimit from 'p-limit';

const limit = pLimit(5); // 最大并发数 5
const urls = [url1, url2, ..., url50];

const tasks = urls.map(url => 
  limit(() => fetch(url).then(res => res.json()))
);

Promise.all(tasks).then(results => {
  console.log('所有请求完成:', results);
});

4. 分批次处理

将请求分成多个批次(Chunk),逐批次执行,适合对实时性要求不高的场景:

javascript
async function batchRequests(urls, batchSize = 5) {
  const results = [];
  for (let i = 0; i < urls.length; i += batchSize) {
    const batch = urls.slice(i, i + batchSize);
    const batchResults = await Promise.all(batch.map(url => fetch(url)));
    results.push(...batchResults);
  }
  return results;
}

// 使用示例
batchRequests(urls, 5).then(results => { ... });

5. 动态调整并发数

根据网络状态或服务器负载动态调整并发数,需结合具体业务逻辑(如错误重试、优先级等):

javascript
class DynamicConcurrency {
  constructor(maxConcurrent = 5) {
    this.maxConcurrent = maxConcurrent;
    this.queue = [];
    this.activeCount = 0;
  }

  add(task) {
    this.queue.push(task);
    this.next();
  }

  next() {
    while (this.queue.length > 0 && this.activeCount < this.maxConcurrent) {
      const task = this.queue.shift();
      this.activeCount++;
      task().finally(() => {
        this.activeCount--;
        this.next();
      });
    }
  }
}

// 使用示例
const controller = new DynamicConcurrency(5);
urls.forEach(url => {
  controller.add(() => fetch(url).then(res => res.json()));
});

关键优化点

  1. 避免浏览器资源耗尽:浏览器对同一域名有并发限制(通常 6-8 个),控制并发可减少排队时间。

  2. 错误处理:在并发控制中加入重试机制(如指数退避)和错误捕获。

  3. 优先级控制:为高优先级请求插队(如用户触发的操作)。

  4. 取消请求:结合 AbortController 允许取消未完成的请求。

总结

  • 简单场景:使用 p-limit 或手动队列。
  • 复杂场景:动态调整并发数、分批次处理或结合优先级。
  • 终极方案:结合并发控制、错误重试、取消机制和性能监控。

通过合理控制并发,可以显著提升前端应用的稳定性和用户体验!