假如有几十个请求,如何去控制并发? 类似图片或文件批量下载

329 阅读2分钟

这是一个WEB 前端的问题, 下面列出几个方案:

在前端处理大量并发请求(如批量下载图片或文件)时,控制并发数可以有效地避免对服务器造成过大压力,同时也能提升用户体验。除了利用 Promise 模拟任务队列的方法,还有一些更高效的方案和策略。以下是几种常见的方法:

1. 使用 Promise 和任务队列

利用 Promise 和任务队列控制并发是一个经典的解决方案。可以创建一个任务队列,控制同时进行的请求数。以下是一个示例:

class TaskQueue {
    constructor(concurrency) {
        this.concurrency = concurrency;
        this.queue = [];
        this.activeCount = 0;
    }

    async addTask(task) {
        if (this.activeCount >= this.concurrency) {
            await new Promise(resolve => this.queue.push(resolve));
        }

        this.activeCount++;
        try {
            await task();
        } finally {
            this.activeCount--;
            if (this.queue.length > 0) {
                this.queue.shift()();
            }
        }
    }
}

// 使用示例
const taskQueue = new TaskQueue(5); // 设置并发数为5

const downloadTask = (url) => () => fetch(url).then(response => response.blob());

const urls = ['url1', 'url2', 'url3', /* ... */];
urls.forEach(url => taskQueue.addTask(downloadTask(url)));

2. 使用 async/await 和限制并发的库

async/await 结合专门的库可以简化并发控制。比如,p-limit 库提供了限制并发执行的功能。

首先安装 p-limit 库:

npm install p-limit

然后使用 p-limit 控制并发:

import pLimit from 'p-limit';

const limit = pLimit(5); // 设置并发数为5

const urls = ['url1', 'url2', 'url3', /* ... */];

const fetchUrl = async (url) => {
    const response = await fetch(url);
    return response.blob();
};

const tasks = urls.map(url => limit(() => fetchUrl(url)));

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

———————————————————————————————————————————————————— 还可以这么用:使用 async 函数和 for...of

利用 async/awaitfor...of 循环,可以手动控制并发:

const createRequest = (url) => {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log(`Downloaded: ${url}`);
            resolve(url);
        }, Math.random() * 2000);
    });
};

const downloadFiles = async (urls, limit) => {
    const results = [];
    const executing = [];

    for (const url of urls) {
        const request = createRequest(url).then(result => {
            results.push(result);
            executing.splice(executing.indexOf(request), 1);
        });
        executing.push(request);

        if (executing.length >= limit) {
            await Promise.race(executing);
        }
    }

    await Promise.all(executing); // 等待剩余的请求完成
    return results;
};

const urls = Array.from({ length: 50 }, (_, i) => `https://example.com/file${i}.jpg`);
downloadFiles(urls, 5).then(results => {
    console.log('All requests completed:', results);
});

3. 使用 Web Workers

Web Workers 可以在后台线程中处理任务,避免阻塞主线程。这对于需要并发操作且处理时间较长的任务(如图片处理)尤其有效。

创建 Web Worker 文件(worker.js):

self.onmessage = async (event) => {
    const { url } = event.data;
    const response = await fetch(url);
    const blob = await response.blob();
    self.postMessage(blob);
};

在主线程中使用 Web Worker:

const worker = new Worker('worker.js');
worker.onmessage = (event) => {
    const blob = event.data;
    // 处理 blob,比如下载文件
};

const urls = ['url1', 'url2', 'url3', /* ... */];
urls.forEach(url => {
    worker.postMessage({ url });
});

4. 使用库或工具

有些第三方库专门用于处理批量请求和并发控制,例如:

  • Axios:可以配合 axios.allaxios.spread 进行请求管理。
  • Bluebird:提供了对 Promise 的扩展,支持并发控制。

5. 使用缓存和节流

  • 缓存:对于相同的请求,缓存可以减少重复请求,从而减少并发量。
  • 节流:控制请求发送的频率,比如使用防抖和节流技术减少请求频率。

这些方法可以根据具体需求和项目情况进行选择和组合使用。不同的场景下,选择适合的控制并发策略能够提高性能和用户体验。