如何实现一个 fetch 请求池?

724 阅读2分钟

介绍

大家好,我是大胆的番茄

请求池是前端面试当中经常会问的一道题目:如何保证同时只有 N 个请求在发送,成功一个就再请求一个?

首先我们要理解为啥要设计一个请求池?主要还是从性能和浏览器并发来考量。浏览器设计当初就定义了浏览器打开页面,同时发送 http 请求的瞬时数量:

浏览器HTTP 1.1HTTP 1.0
Chrome、Firefox66
Safari44

从上表可以看出,Chrome 最大可以同时发送 6 个请求,超过之后会有明显的等待时间。

所以我们从面试题开始看如何实现请求池,设计一个请求池,让下面的代码可以正常运行:

const fetchPool = new FetchPool({
    max: 3,
});

for (let i = 0; i < 10; i++) {
    fetchPool('https://uquuu.com/yesorno')
        .then((value) => {
            console.log('value :>> ', i, value);
        })
        .catch((error) => {
            console.log('error :>> ', i, error);
        });
}

废话不多说,上代码。

实现

class FetchPool {
    // 默认配置
    options = {
        // 最大同时请求数量
        max: 6,
    };
    // 队列(待请求的任务列表)
    queues = [];
    // 计数(正在请求中的数量)
    count = 0;

    constructor(options) {
        this.options = { ...this.options, ...options };
        /**
         * 返回 fetch 函数
         *
         * @see https://developer.mozilla.org/zh-CN/docs/Web/API/fetch
         */
        return (input, init) => {
            // fetch 返回一个 promise
            return new Promise((resolve, reject) => {
                // 将这次请求的 input/init,promise 的 resolve/reject 放入队列,等待处理
                this.queues.push({
                    input,
                    init,
                    resolve,
                    reject,
                });
                // 处理队列
                this.runQueues();
            })
        }
    }

    runQueues() {
        // 判断队列中是否还有未请求的任务,没有则返回
        if (this.queues.length === 0) return;
        // 判断正在请求中的数量是否大于等于最大请求数,是则返回
        if (this.count >= this.options.max) return;

        // 将正在请求中的数量 +1
        this.count++;
        // 从队列中取出一个任务进行处理
        const queue = this.queues.shift();
        // 处理请求
        fetch(queue.input, queue.init).then((res) => {
            // 处理结束,将正在请求中的数量 -1
            this.count--;
            // 继续处理队列
            this.runQueues();
            // 返回请求结果
            return queue.resolve(res);
        }).catch((err) => {
            // 处理结束,将正在请求中的数量 -1
            this.count--;
            // 继续处理队列
            this.runQueues();
            // 返回请求结果
            return queue.reject(err);
        })
    }
}