将反应客户端发出的请求数量限制为 API
Rate limit the number of request made from react client to API
我在客户端中使用 React 和 fetch 向 Discogs API 发出请求。在这个 API 中,每分钟最多有 60 个请求的限制。为了管理这个 Discogs 在响应 headers 上添加自定义值,如“剩余请求”、“已用请求”或“最大允许请求”,但由于 cors headers 无法读取。
所以我决定做的是为此 API 创建一个请求包装器,从那里我可以:
- 定义时间 window(在本例中为 60 秒)。
- 定义此时允许的最大请求数window。
- Queue 收到的请求按限制处理。
- 能够取消请求并将它们拉出 queue。
我设法使用单例 Object 做了一个工作示例,其中作业是 queued 并使用 setTimeout
函数进行管理以延迟请求的调用。
这在使用简单回调时对我有用,但我不知道如何return React 组件的值以及如何使用 Promises 而不是回调来实现它(获取)。
我也不知道如何取消响应组件的超时或获取请求。
您可以查看 this example,我在其中进行了简化。我知道这可能不是最好的方法,或者这段代码很糟糕。这就是为什么任何帮助或指导将不胜感激。
你不需要setTimout
(所以你不需要cancel the setTimeout), and you do not need to 。
要在 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>;
})}
</>);
};
我想限制请求的数量,但也想将它们搁置,直到 API 允许为止,所以我认为最好的选择是 运行 它们在 FIFO 中顺序顺序,它们之间有 1 秒的延迟,因此我不会超过 1 分钟内 60 个请求的要求。我也在考虑让他们运行其中一些并发,但在这种情况下,一旦达到限制,等待时间可能会很长。
然后我创建了 2 个东西:
一个'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 单例 class
- 它会将任何收到的请求排入数组。
- 请求将一次处理一个,请求之间的超时为 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;
我不知道这是否是最佳解决方案,但它完成了工作。
我在客户端中使用 React 和 fetch 向 Discogs API 发出请求。在这个 API 中,每分钟最多有 60 个请求的限制。为了管理这个 Discogs 在响应 headers 上添加自定义值,如“剩余请求”、“已用请求”或“最大允许请求”,但由于 cors headers 无法读取。
所以我决定做的是为此 API 创建一个请求包装器,从那里我可以:
- 定义时间 window(在本例中为 60 秒)。
- 定义此时允许的最大请求数window。
- Queue 收到的请求按限制处理。
- 能够取消请求并将它们拉出 queue。
我设法使用单例 Object 做了一个工作示例,其中作业是 queued 并使用 setTimeout
函数进行管理以延迟请求的调用。
这在使用简单回调时对我有用,但我不知道如何return React 组件的值以及如何使用 Promises 而不是回调来实现它(获取)。
我也不知道如何取消响应组件的超时或获取请求。
您可以查看 this example,我在其中进行了简化。我知道这可能不是最好的方法,或者这段代码很糟糕。这就是为什么任何帮助或指导将不胜感激。
你不需要setTimout
(所以你不需要cancel the setTimeout), and you do not need to 。
要在 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>;
})}
</>);
};
我想限制请求的数量,但也想将它们搁置,直到 API 允许为止,所以我认为最好的选择是 运行 它们在 FIFO 中顺序顺序,它们之间有 1 秒的延迟,因此我不会超过 1 分钟内 60 个请求的要求。我也在考虑让他们运行其中一些并发,但在这种情况下,一旦达到限制,等待时间可能会很长。
然后我创建了 2 个东西:
一个'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 单例 class
- 它会将任何收到的请求排入数组。
- 请求将一次处理一个,请求之间的超时为 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;
我不知道这是否是最佳解决方案,但它完成了工作。