2024前端面试系列---假如有几百个请求 如何控制并发

472 阅读2分钟

当需要控制前端发起的几百个请求时,直接发送所有请求会导致服务器压力过大,也可能对浏览器和网络造成性能问题。为了高效地管理这些请求,可以采用几种方法来控制请求的并发数。常见的策略包括:

1. 使用队列和批量请求

将所有请求分成多个批次,限制每批次的请求数量,然后按批次依次发送请求。

示例代码:

async function sendRequestsInBatches(requests, batchSize) {
  for (let i = 0; i < requests.length; i += batchSize) {
    // 获取当前批次的请求
    const batch = requests.slice(i, i + batchSize);

    // 发送当前批次的请求
    await Promise.all(batch.map(req => req()));

    console.log(`Batch ${Math.floor(i / batchSize) + 1} complete`);
  }
}

// 使用示例
const requests = [
  () => fetch('/api/endpoint1'),
  () => fetch('/api/endpoint2'),
  // 更多请求
];

sendRequestsInBatches(requests, 10); // 每批次最多发送 10 个请求

解释:

  • 将所有请求放在一个队列中,每次取出 batchSize 个请求并发执行。
  • Promise.all 用于并行执行每批次的请求,await 确保当前批次请求完成后才会继续下一批次。

2. 使用限制并发的工具库(如 p-limitPromise Pool

有一些工具库可以方便地限制并发请求数量,比如 p-limit,它允许你控制并发执行的 Promise 数量。

示例代码(使用 p-limit 库):

import pLimit from 'p-limit';

const limit = pLimit(10); // 限制最大并发为 10

async function sendRequests(requests) {
  // 使用 limit 包装每个请求,确保并发数不超过 10
  const result = await Promise.all(requests.map(req => limit(() => req())));
  console.log('All requests complete');
  return result;
}

// 使用示例
const requests = [
  () => fetch('/api/endpoint1'),
  () => fetch('/api/endpoint2'),
  // 更多请求
];

sendRequests(requests);

解释:

  • 使用 p-limit 库可以限制最大并发数量,limit 包装每个请求,使得同时并发的请求数不会超过指定的数量。

3. 使用 async/await 和递归实现串行批次执行

如果需要对请求进行更细粒度的控制,也可以手动通过 async/await 和递归来处理请求。

示例代码:

async function sendRequestsWithLimit(requests, batchSize) {
  let index = 0;

  // 递归发送请求
  async function sendBatch() {
    const batch = requests.slice(index, index + batchSize);
    await Promise.all(batch.map(req => req()));

    index += batch.length;
    if (index < requests.length) {
      // 如果还有未处理的请求,递归调用
      await sendBatch();
    }
  }

  await sendBatch();
  console.log('All requests completed');
}

// 使用示例
const requests = [
  () => fetch('/api/endpoint1'),
  () => fetch('/api/endpoint2'),
  // 更多请求
];

sendRequestsWithLimit(requests, 10); // 每次发送 10 个请求

解释:

  • 使用递归和 Promise.all 来控制并发数量。每次发送的请求数量不超过 batchSize,直到所有请求完成。

4. 使用 Web Worker 来分担并发负载

如果请求的任务计算量大,可以考虑使用 Web Worker 来并行处理请求,以避免阻塞主线程。Web Worker 适用于 CPU 密集型任务,但如果只是发送网络请求,通常不需要。

5. 结合 setTimeoutsetInterval 进行节流

如果想要对请求进行更灵活的控制,也可以结合 setTimeoutsetInterval 来间隔一定时间发送请求,避免瞬间发起过多请求。

示例代码:

async function sendRequestsWithDelay(requests, delay) {
  for (let i = 0; i < requests.length; i++) {
    await requests[i]();
    console.log(`Request ${i + 1} completed`);
    if (i < requests.length - 1) {
      // 等待一段时间再发送下一个请求
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  console.log('All requests completed');
}

// 使用示例
const requests = [
  () => fetch('/api/endpoint1'),
  () => fetch('/api/endpoint2'),
  // 更多请求
];

sendRequestsWithDelay(requests, 1000); // 每个请求之间间隔 1000ms

解释:

  • 通过 setTimeoutnew Promise(resolve => setTimeout(resolve, delay)) 控制每个请求之间的延迟时间,防止过多请求同时发送。

6. 使用 Promise.allSettledPromise.race 来处理请求状态

  • 如果你不关心请求是否成功,只关心请求是否完成,可以使用 Promise.allSettled 来处理所有请求的完成情况。
  • 如果你只关心第一个请求完成,可以使用 Promise.race

示例代码(使用 Promise.allSettled):

const requests = [
  () => fetch('/api/endpoint1'),
  () => fetch('/api/endpoint2'),
  // 更多请求
];

Promise.allSettled(requests.map(req => req()))
  .then(results => {
    console.log('All requests completed', results);
  });

总结:

  • 批量请求:将请求分批次执行,每批次限制并发数量。
  • p-limit:使用 p-limit 等库来直接限制并发数。
  • 递归和 async/await:通过递归或 setTimeout 控制请求节奏和数量。
  • Web Worker:适合计算密集型任务,但不适合普通的网络请求。

这些方法可以有效地控制前端请求的并发量,避免请求过多导致服务器压力过大或浏览器性能问题。