前端如何控制并发请求

在前端开发中,一次性发起大量网络请求可能会导致以下问题:

  • 浏览器的并发连接数限制

  • 服务器压力过大、服务器过载

  • 用户设备资源消耗过高

  • 网络拥塞

首先用 nodejs 搭建一个模拟服务器
const express = require("express");
const cors = require("cors");
const app = express();
const PORT = 3000;

// 启用CORS以允许前端访问
app.use(cors());

// 记录并发请求的状态
let activeRequests = 0;
let maxConcurrent = 0;
let totalRequests = 0;
let requestLog = [];

// 模拟延迟的API端点
app.get("/api/test/:id", (req, res) => {
  const id = req.params.id;
  const delay = parseInt(req.query.delay) || 2000; // 默认延迟2秒

  // 记录请求开始
  const startTime = Date.now();
  activeRequests++;
  totalRequests++;

  // 更新最大并发数
  if (activeRequests > maxConcurrent) {
    maxConcurrent = activeRequests;
  }

  console.log(`请求 #${id} 开始处理 (当前并发: ${activeRequests})`);

  // 模拟API延迟
  setTimeout(() => {
    // 请求完成
    activeRequests--;
    const endTime = Date.now();

    // 记录到日志
    requestLog.push({
      id,
      startTime,
      endTime,
      duration: endTime - startTime,
    });

    console.log(`请求 #${id} 完成 (剩余并发: ${activeRequests})`);

    // 返回结果
    res.json({
      requestId: id,
      message: `请求 #${id} 成功完成`,
      duration: endTime - startTime,
    });
  }, delay);
});

// 获取服务器统计信息
app.get("/api/stats", (req, res) => {
  res.json({
    activeRequests,
    maxConcurrent,
    totalRequests,
    requestLog,
  });
});

// 重置统计信息
app.post("/api/reset", (req, res) => {
  activeRequests = 0;
  maxConcurrent = 0;
  totalRequests = 0;
  requestLog = [];

  res.json({ message: "统计数据已重置" });
});

app.listen(PORT, () => {
  console.log(`测试服务器运行在 http://localhost:${PORT}`);
});

以下是几种常见的控制并发请求的方法
1、请求队列+并发控制

// 请求控制器类
class RequestController {
  constructor(maxConcurrent = 5) {
    this.maxConcurrent = maxConcurrent; // 最大并发数
    this.queue = []; // 请求队列
    this.runningCount = 0; // 当前运行的请求数
  }

  async add(promiseFunc) {
    if (this.runningCount >= this.maxConcurrent) {
      // 等待执行
      await new Promise((resolve) => this.queue.push(resolve));
    }

    return this._run(promiseFunc);
  }

  async _run(promiseFunc) {
    this.runningCount++;

    try {
      return await promiseFunc();
    } finally {
      this.runningCount--;

      // 执行队列中的下一个请求
      if (this.queue.length > 0) {
        const next = this.queue.shift();
        next();
      }
    }
  }
}

// 使用示例
const controller = new RequestController(3); // 并发控制为3

// 模拟大量请求
const requests = Array(40)
  .fill(0)
  .map((_, index) => {
    return () => {
      return fetch(`http://localhost:3000/api/test/${index}`).then((res) =>
        res.json()
      );
    };
  });

// 发起所有请求,但控制并发数
Promise.all(requests.map((req) => controller.add(req))).then((results) =>
  console.log("所有请求完成")
);

2、分批发送请求

async function sendRequestsInBatches(requests, batchSize = 5) {
  const results = [];

  for (let i = 0; i < requests.length; i += batchSize) {
    const batch = requests.slice(i, i + batchSize);
    const batchResults = await Promise.all(batch.map((req) => req()));
    results.push(...batchResults);
  }

  return results;
}
// 使用示例
// 模拟大量请求
const requests = Array(40)
  .fill(0)
  .map((_, index) => {
    return () => {
      return fetch(`http://localhost:3000/api/test/${index}`).then((res) =>
        res.json()
      );
    };
  });
(async function () {
  const results = await sendRequestsInBatches(requests, 8);
})();

3、使用信号量控制

class Semaphore {
  constructor(max) {
    this.max = max;
    this.count = 0;
    this.queue = [];
  }

  async acquire() {
    if (this.count < this.max) {
      this.count++;
      return Promise.resolve();
    }

    return new Promise((resolve) => this.queue.push(resolve));
  }

  release() {
    this.count--;

    if (this.queue.length > 0) {
      this.count++;
      const resolve = this.queue.shift();
      resolve();
    }
  }
}
// 模拟大量请求
const requests = Array(40)
  .fill(0)
  .map((_, index) => {
    return () => {
      return fetch(`http://localhost:3000/api/test/${index}`).then((res) =>
        res.json()
      );
    };
  });

// 使用信号量控制 示例
async function useSemaphore(requests, limit) {
  const semaphore = new Semaphore(limit);

  return Promise.all(
    requests.map(async (req) => {
      await semaphore.acquire();
      try {
        return await req();
      } finally {
        semaphore.release();
      }
    })
  );
}
(async function () {
  const results = await useSemaphore(requests, 5);
})();

5、async/await 控制

async function sendWithConcurrencyLimit(urls, limit = 5) {
  const results = [];
  const executing = new Set();

  for (const url of urls) {
    const p = fetch(url)
      .then((r) => r.json())
      .then((data) => {
        executing.delete(p);
        return data;
      });

    executing.add(p);
    results.push(p);

    if (executing.size >= limit) {
      // 等待任意一个请求完成
      await Promise.race(executing);
    }
  }

  // 等待所有请求完成
  return Promise.all(results);
}
// 模拟大量请求
const requests = Array(40)
  .fill(0)
  .map((_, index) => {
    return () => {
      return fetch(`http://localhost:3000/api/test/${index}`).then((res) =>
        res.json()
      );
    };
  });

(async function () {
  const results = await sendWithConcurrencyLimit(requests, 5);
})();

6、使用第三方库

// 使用 p-limit
import pLimit from "p-limit";

const limit = pLimit(5); // 最大并发数为5

const requests = Array(50)
  .fill(0)
  .map((_, i) => {
    return () =>
      fetch(`http://localhost:3000/api/test/${index}`).then((r) => r.json());
  });

Promise.all(requests.map((req) => limit(() => req()))).then((results) =>
  console.log("所有请求完成")
);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值