我在客户端使用React和fetch向Discogs API发出请求。在这个API中,每分钟最多有60个请求。要管理这个diskgs,需要添加自定义值,如"剩余请求"、"已使用请求"等。或"最大允许的请求",在响应头上,但由于错误,这些头无法读取。
所以我决定做的是为这个API创建一个请求包装器,从我可以:
- 定义一个时间窗口(在本例中为60秒)。
- 定义允许在此时间窗口内执行的最大请求。
- 根据限制将接收到的请求排队等待处理。
- 能够取消请求并将其从队列中拉出。
我已经成功地使用单例对象做了一个工作示例,其中作业排队并使用setTimeout
函数进行管理,以延迟请求的调用。
当使用简单的回调时,这对我来说是有效的,但我不知道如何返回一个值给React组件,以及如何用Promises来实现它,而不是回调(获取)。
我也不知道如何取消超时或从反应组件获取请求.
你可以看看这个例子,我已经简化了它。我知道这可能不是最好的方法或者这段代码很糟糕。这就是为什么任何帮助或指导,我将非常感激。
我想限制请求的数量,但也把它们搁置,直到API允许为止,所以我认为最好的选择是以FIFO顺序依次运行它们,它们之间的延迟为1秒,所以我不超过60个请求在1分钟的要求。我也在考虑让他们并发地运行其中的一些,但是在这种情况下,一旦达到限制,等待时间可能会很长。
我创建了两个东西:
一个'useDiscogsFetch'钩子
- 将所有API调用作为承诺发送给队列,而不是直接进行。
- 它还将生成一个UUID来标识请求,以便在需要时能够取消它。为此,我使用了uuid npm包。
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;
一个discogqueue单例类
- 它将任何收到的请求排队到数组中。
- 请求将每次处理一个,它们之间的超时时间为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;
我不知道这是不是最好的解决办法,但它能把工作完成。