admin管理员组

文章数量:1325236

I'm using React and fetch in the client to make requests to the Discogs API. In this API, there's a limit of max 60 request per minute. For managing this Discogs is adding custom values like "remaining requests", "used requests" or "maximum allowed requests", on the response headers but due to cors those headers cannot be readed.

So what I decided to do is to create a request wrapper for this API, from where I could:

  • Define a time window (in this case 60 secs).
  • Define the max requests allowed to do in this time window.
  • Queue the received requests to be processed according to the limits.
  • Be able to cancel the requests and pull them out of the queue.

I've managed to do a working example using a singleton Object where the jobs are queued and managed with setTimeout function to delay the call of the request.

This works for me when using simple callbacks, but I don't know how to return a value to the React ponent and how to implement it with Promises instead of callbacks (fetch).

I also don't know how to cancel the timeout or the fetch request from the react ponent.

You can check this example, where I've simplified it. I know that maybe that's not the best way to do it or maybe this code is shit. That's why any help or guidance on it would be very much appreciated.

I'm using React and fetch in the client to make requests to the Discogs API. In this API, there's a limit of max 60 request per minute. For managing this Discogs is adding custom values like "remaining requests", "used requests" or "maximum allowed requests", on the response headers but due to cors those headers cannot be readed.

So what I decided to do is to create a request wrapper for this API, from where I could:

  • Define a time window (in this case 60 secs).
  • Define the max requests allowed to do in this time window.
  • Queue the received requests to be processed according to the limits.
  • Be able to cancel the requests and pull them out of the queue.

I've managed to do a working example using a singleton Object where the jobs are queued and managed with setTimeout function to delay the call of the request.

This works for me when using simple callbacks, but I don't know how to return a value to the React ponent and how to implement it with Promises instead of callbacks (fetch).

I also don't know how to cancel the timeout or the fetch request from the react ponent.

You can check this example, where I've simplified it. I know that maybe that's not the best way to do it or maybe this code is shit. That's why any help or guidance on it would be very much appreciated.

Share Improve this question edited Feb 24, 2021 at 15:24 giorgiline asked Jan 22, 2021 at 18:12 giorgilinegiorgiline 1,3816 gold badges22 silver badges36 bronze badges 4
  • 1 setTimeout function returns id of the timer that can be later cancelled with clearTimeout call. You can maintain a map with results of the fetch mapped to the timeout id and make react ponent work with that map. On API call in the ponent just return id of the timer and work with that. – Monsieur Merso Commented Jan 22, 2021 at 18:19
  • uhm... did you decide to do it? or are you asking us to do it for you. You need to use try catch blocks, and look into async awaits. Using a setTimeout is not a good way to hold pending requests. log the first request, log the time of the first request. let those other things run as fast as they can and IF there have been 60 requests, in less than 1 minute don't send the request until a minute has passed. – akili Sosa Commented Jan 22, 2021 at 18:22
  • @akiliSosa obviously as I've said I'm looking for any guidance on how to properly do it, and improve over what I already have. – giorgiline Commented Jan 22, 2021 at 18:52
  • giorgiline idk man you didn't post what you had, so i wasn't sure where you were at. The guy that posted below @kca has a shown a pretty good solution. – akili Sosa Commented Jan 23, 2021 at 19:32
Add a ment  | 

1 Answer 1

Reset to default 3

I wanted to limit the number of requests but also put them on hold until it is allowed by the API, so I though that the best option was to run them sequentially in a FIFO order, with a delay of 1 sec between them so I do not exceed the 60 requests in 1 minute requirement. I was also thinking about let them run some of them concurrently, but in this case the waiting time could be high once the limit is reached.

I created then 2 things:

A 'useDiscogsFetch' hook

  • Will send all the API calls as promises to the queue instead of making them directly.
  • It will also generate an UUID to identify the request to be able to cancel it if it's needed. For this I used the 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;

A DiscogsQueue singleton class

  • It will enqueue any received request into an Array.
  • The requests will be processed one at a time with a timeout of 1 sec between them starting always with the oldest.
  • It has also a remove method, that will search for an id and remove the request from the array.

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;

I don't know if it's the best solution but it gets the job done.

本文标签: javascriptRate limit the number of request made from react client to APIStack Overflow