【问题标题】:Rate limit the number of request made from react client to API速率限制从反应客户端向 API 发出的请求数
【发布时间】:2021-04-27 05:43:02
【问题描述】:

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

所以我决定做的是为这个 API 创建一个请求包装器,我可以:

  • 定义时间窗口(在本例中为 60 秒)。
  • 定义在此时间窗口内允许执行的最大请求数。
  • 将收到的请求按照限制排队处理。
  • 能够取消请求并将它们拉出队列。

我已经设法使用单例对象做了一个工作示例,其中作业排队并使用setTimeout 函数进行管理,以延迟请求的调用。

这适用于我在使用简单回调时,但我不知道如何将值返回到 React 组件以及如何使用 Promises 而不是回调来实现它(获取)。

我也不知道如何取消超时或来自 react 组件的 fetch 请求

您可以查看this example,我已在此处对其进行了简化。我知道这可能不是最好的方法,或者这段代码很糟糕。这就是为什么任何有关它的帮助或指导都会非常感激的原因。

【问题讨论】:

  • setTimeout 函数返回计时器的 ID,以后可以通过 clearTimeout 调用取消。您可以维护一个映射,并将获取的结果映射到超时 id,并使反应组件与该映射一起工作。在组件中调用 API 时,只需返回计时器的 id 并使用它。
  • 嗯...你决定这样做了吗?还是您要求我们为您做这件事。您需要使用 try catch 块,并查看异步等待。使用 setTimeout 不是保存待处理请求的好方法。记录第一个请求,记录第一个请求的时间。让其他事情尽可能快地运行,如果有 60 个请求,则在不到 1 分钟的时间内不要发送请求,直到一分钟过去。
  • @akiliSosa 显然正如我所说的,我正在寻找有关如何正确执行此操作的任何指导,并改进我已有的。
  • giorgiline idk 伙计,你没有发布你拥有的东西,所以我不确定你在哪里。在@kca 下面发布的那个人已经展示了一个非常好的解决方案。

标签: javascript reactjs discogs-api


【解决方案1】:

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

我创造了两件事:

“useDiscogsFetch”挂钩

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

useDiscogsFetch.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 秒,始终从最旧的开始。
  • 它还有一个 remove 方法,它将搜索一个 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;

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

【讨论】:

    【解决方案2】:

    你不需要setTimout(所以你不需要cancel the setTimeout),你也不需要cancel the fetch

    要在 React 组件中使用值,您必须使用 React 状态。 React 不会知道对某些外部对象(例如您的单例对象)的更改。

    您可以存储最后n个请求的时间戳,如果第一个比时间段更旧,您可以将其删除并发出新请求。

    const useLimitedRequests = function(){
        const limit = 5;
        const timePeriod = 6 * 1000;
        const [ requests, setRequests ] = useState([]);
    
        return [
            requests,
            function(){
                const now = Date.now();
    
                if( requests.length > 0 && (requests[0] < now - timePeriod) ){
                    setRequests( requests.slice(1) );
                }
    
                if( requests.length < limit ){
                    setRequests([ ...requests, now ]);
                    return now;
                }
    
                return 0;
            }
        ];
    };
    
    export const LimitedRequests = (props)=>{
        const [ requests, addRequest ] = useLimitedRequests();
        return (<>
    
            <button onClick={ ()=>{
                if( addRequest() > 0 ){
                    console.log('ok, do fetch again');
                } else {
                    console.log('no no, you have to wait');
                }
            }}>
                fetch again
            </button>
    
            { requests.map(function( req ){
                return <div key={ req }>{ req }</div>;
            })}
        </>);
    };
    

    【讨论】:

    • 为什么不需要取消请求?例如,如果发出请求的组件在收到响应之前卸载,这不是一件好事吗?
    • 您在此处显示的这种情况将是一件好事,仅用于限制正在发出的请求数量,但是如果您只想在达到限制时将它们搁置怎么办然后在可能的情况下继续挂起的呼叫?
    • 你是对的,我没有正确阅读你的问题。我去掉了“不需要取消请求”,剩下的也不够用。有一天我可能会更新我的答案,但一次回答两个问题而不是一个问题总是更难。
    • 是的,很抱歉。我喜欢使用钩子来处理组件内部的数据。我还想,如果我想在不同的组件之间共享钩子,我也应该在 React 上下文中使用它,不是吗?我会努力解决这个问题,并在我有时间的时候更新。感谢您的反馈。
    猜你喜欢
    • 2021-02-24
    • 2014-03-20
    • 2022-08-23
    • 2016-09-21
    • 1970-01-01
    • 2015-10-15
    • 1970-01-01
    • 2021-10-04
    • 2021-05-27
    相关资源
    最近更新 更多