速率限制React客户端向API发出的请求数量

2023-12-02

我在客户端使用 React 和 fetch 向Discogs API。在此 API 中,每分钟最多有 60 个请求的限制。为了管理此Discogs,在响应标头中添加自定义值,例如“剩余请求”、“已使用的请求”或“最大允许请求”,但由于cors,这些标头无法读取。

因此,我决定为此 API 创建一个请求包装器,从中我可以:

  • 定义一个时间窗口(在本例中为 60 秒)。
  • 定义在此时间窗口内允许执行的最大请求数。
  • 根据限制对接收到的请求进行排队以进行处理。
  • 能够取消请求并将其从队列中拉出。

我已经成功地使用单例对象完成了一个工作示例,其中作业通过以下方式排队和管理setTimeout延迟请求调用的函数。

当使用简单的回调时,这对我有用,但我不知道如何向 React 组件返回值以及如何使用 Promises 而不是回调来实现它(拿来)。

我也不知道如何取消反应组件的超时或获取请求.

你可以检查这个例子,我已经简化了它。我知道这也许不是最好的方法,或者这段代码很糟糕。这就是为什么任何有关它的帮助或指导将非常感激。


我想限制请求的数量,但也将它们搁置起来,直到 API 允许为止,所以我认为最好的选择是以 FIFO 顺序依次运行它们,它们之间有 1 秒的延迟,所以我这样做不超过 1 分钟内 60 个请求的要求。我也在考虑让他们同时运行其中一些,但在这种情况下,一旦达到限制,等待时间可能会很长。

然后我创建了两件事:

'useDiscogsFetch' 钩子

  • 将所有 API 调用作为 Promise 发送到队列,而不是直接进行调用。
  • 它还会生成一个 UUID 来标识请求,以便能够在需要时取消它。为此我使用了uuid npm 包.

使用DiscogsFetch.js

import { useEffect, useRef, useState } from 'react';
import DiscogsQueue from '@/utils/DiscogsQueue';
import { v4 as uuidv4 } from 'uuid';

const useDiscogsFetch = (url, fetcher) => {

    const [data, setData] = useState(null);
    const [error, setError] = useState(null);
    const requestId = useRef();

    const cancel = () => {
        DiscogsQueue.removeRequest(requestId.current);
    }

    useEffect(() => {
        requestId.current = uuidv4();
        const fetchData = async () => {
            try {
                const data = await DiscogsQueue.pushRequest(
                    async () => await fetcher(url),
                    requestId.current
                );
                setData(data)
            } catch (e) {
                setError(e);
            }
        };
        fetchData();
        return () => {
            cancel();
        };
    }, [url, fetcher]);

    return {
        data,
        loading: !data && !error,
        error,
        cancel,
    };

};

export default useDiscogsFetch;

DiscogsQueue 单例类

  • 它将把任何收到的请求放入数组中。
  • 请求将一次处理一个,请求之间有 1 秒的超时时间,始终从最早的请求开始。
  • 它还有一个删除方法,该方法将搜索 id 并从数组中删除请求。

DiscogsQueue.js

class DiscogsQueue {

    constructor() {
        this.queue = [];
        this.MAX_CALLS = 60;
        this.TIME_WINDOW = 1 * 60 * 1000; // min * seg * ms
        this.processing = false;
    }

    pushRequest = (promise, requestId) => {
        return new Promise((resolve, reject) => {
            // Add the promise to the queue.
            this.queue.push({
                requestId,
                promise,
                resolve,
                reject,
            });

            // If the queue is not being processed, we process it.
            if (!this.processing) {
                this.processing = true;
                setTimeout(() => {
                    this.processQueue();
                }, this.TIME_WINDOW / this.MAX_CALLS);
            }
        }
        );
    };

    processQueue = () => {
        const item = this.queue.shift();
        try {
            // Pull first item in the queue and run the request.
            const data = item.promise();
            item.resolve(data);
            if (this.queue.length > 0) {
                this.processing = true;
                setTimeout(() => {
                    this.processQueue();
                }, this.TIME_WINDOW / this.MAX_CALLS);
            } else {
                this.processing = false;
            }
        } catch (e) {
            item.reject(e);
        }
    };

    removeRequest = (requestId) => {
        // We delete the promise from the queue using the given id.
        this.queue.some((item, index) => {
            if (item.requestId === requestId) {
                this.queue.splice(index, 1);
                return true;
            }
        });
    }
}

const instance = new DiscogsQueue();
Object.freeze(DiscogsQueue);

export default instance;

我不知道这是否是最好的解决方案,但它可以完成工作。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

速率限制React客户端向API发出的请求数量 的相关文章

随机推荐