前端假如有几十个请求,如何去控制并发

1,488 阅读2分钟

在处理大量并发请求时,为了避免服务器过载或客户端性能下降,可以使用并发控制技术。以下是几种常见的方式:


1. 使用 Promise.allSettled 和分批处理

将请求分成小批次,并逐批执行,每一批的大小由并发限制决定:

async function fetchWithConcurrency(urls, maxConcurrency) {
    const results = [];
    while (urls.length > 0) {
        // 取出一批URL
        const batch = urls.splice(0, maxConcurrency);
        // 使用 Promise.allSettled 执行一批请求
        const batchResults = await Promise.allSettled(
            batch.map(url => fetch(url))
        );
        results.push(...batchResults);
    }
    return results;
}

const urls = Array(100).fill('https://example.com/api');
fetchWithConcurrency(urls, 5).then(results => {
    console.log(results); // 输出每个请求的状态与结果
});

2. 使用队列方式控制并发

创建一个队列控制器,通过限制同时运行的 Promise 数量来管理并发:

class PromiseQueue {
    constructor(maxConcurrency) {
        this.maxConcurrency = maxConcurrency;
        this.running = 0;
        this.queue = [];
    }

    enqueue(task) {
        return new Promise((resolve, reject) => {
            this.queue.push(() => task().then(resolve, reject));
            this.runNext();
        });
    }

    runNext() {
        if (this.running >= this.maxConcurrency || this.queue.length === 0) {
            return;
        }
        const task = this.queue.shift();
        this.running++;
        task().finally(() => {
            this.running--;
            this.runNext();
        });
    }
}

// 使用队列
const queue = new PromiseQueue(5); // 并发数为 5
const urls = Array(100).fill('https://example.com/api');

Promise.all(
    urls.map(url => queue.enqueue(() => fetch(url)))
).then(results => {
    console.log(results);
});

3. 使用第三方库

可以使用现成的库如 p-limitpromise-pool 来管理并发。

示例:使用 p-limit

const pLimit = require('p-limit');

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

const urls = Array(100).fill('https://example.com/api');
const tasks = urls.map(url => limit(() => fetch(url)));

Promise.all(tasks).then(results => {
    console.log(results);
});

示例:使用 promise-pool

const { promisePool } = require('promise-pool');

const urls = Array(100).fill('https://example.com/api');

async function fetchUrl(url) {
    const response = await fetch(url);
    return response.json();
}

promisePool({ items: urls, concurrency: 5, task: fetchUrl })
    .then(results => {
        console.log(results);
    });

4. 浏览器专用:AbortController 限制超时

结合超时机制,使用 AbortController 提前中止请求,防止某些请求过长拖慢整个流程:

async function fetchWithTimeout(url, timeout = 5000) {
    const controller = new AbortController();
    const signal = controller.signal;
    const timeoutId = setTimeout(() => controller.abort(), timeout);

    try {
        const response = await fetch(url, { signal });
        return await response.json();
    } catch (err) {
        return { error: 'Timeout or network error' };
    } finally {
        clearTimeout(timeoutId);
    }
}

// 并发控制逻辑同上

选择建议:

  • 任务数量多,但单任务时间较短: 分批或 PromiseQueue 更适合。
  • 任务数量多且复杂: 使用 p-limit 等库实现并发控制。
  • 实时性要求高: 考虑 AbortController 或合理设置超时策略。