函数如何限制其调用速率?如果调用过于频繁,则不应丢弃这些调用,而是应排队并按时间间隔(间隔X毫秒)。我研究过throught和debounce,但它们会丢弃呼叫,而不是排队等待将来运行。
有比以X毫秒为间隔设置process()
方法的队列更好的解决方案吗?JS框架中有这样的标准实现吗?到目前为止,我已经看了undercore.js,什么都没有。
如果没有库,应该相当简单:
var stack = [],
timer = null;
function process() {
var item = stack.shift();
// process
if (stack.length === 0) {
clearInterval(timer);
timer = null;
}
}
function queue(item) {
stack.push(item);
if (timer === null) {
timer = setInterval(process, 500);
}
}
http://jsfiddle.net/6TPed/4/
下面是一个示例,它将this
(或允许您设置自定义的)
function RateLimit(fn, delay, context) {
var canInvoke = true,
queue = [],
timeout,
limited = function () {
queue.push({
context: context || this,
arguments: Array.prototype.slice.call(arguments)
});
if (canInvoke) {
canInvoke = false;
timeEnd();
}
};
function run(context, args) {
fn.apply(context, args);
}
function timeEnd() {
var e;
if (queue.length) {
e = queue.splice(0, 1)[0];
run(e.context, e.arguments);
timeout = window.setTimeout(timeEnd, delay);
} else
canInvoke = true;
}
limited.reset = function () {
window.clearTimeout(timeout);
queue = [];
canInvoke = true;
};
return limited;
}
现在
function foo(x) {
console.log('hello world', x);
}
var bar = RateLimit(foo, 1e3);
bar(1); // logs: hello world 1
bar(2);
bar(3);
// undefined, bar is void
// ..
// logged: hello world 2
// ..
// logged: hello world 3
虽然其他人提供的代码片段确实有效(我已经基于它们构建了一个库),但对于那些想要使用支持良好的模块的人来说,以下是最佳选择:
- 最流行的速率限制器是限制器
- function-rate-limit有一个简单的API可以工作,并且在npmjs上有很好的使用统计数据
- valvallet,一个较新的模块,声称通过支持承诺做得更好,但还没有受到欢迎
我需要一个TypeScript版本,所以我用了@Dan Dasselescu的fiddle并添加了类型。
如果你能提高打字能力,请留言🙏
function rateLimit<T>(
fn: (...args: Array<any>) => void,
delay: number,
context?: T
) {
const queue: Array<{ context: T; arguments: Array<any> }> = []
let timer: NodeJS.Timeout | null = null
function processQueue() {
const item = queue.shift()
if (item) {
fn.apply<T, Array<any>, void>(item.context, item.arguments)
}
if (queue.length === 0 && timer) {
clearInterval(timer)
timer = null
}
}
return function limited(this: T, ...args: Array<any>) {
queue.push({
context: context || this,
arguments: [...args],
})
if (!timer) {
processQueue() // start immediately on the first invocation
timer = setInterval(processQueue, delay)
}
}
}
我认为这可以变得更简单,并且可以与装饰器一起使用:
/**
* Sleeps async for a given amount of time.
* @param milisec
* @returns
*/
function asyncDelay(milisec: number): Promise<void> {
return new Promise<void>((resolve) => {
setTimeout(() => { resolve(); }, milisec);
});
}
/**
* Generates a random int within the max and min range.
* Maximum is exclusive and minimum is inclusive.
* @param min
* @param max
*/
export const getRandomInt = (
min: number,
max: number,
): number => (
Math.floor(
Math.random() * (
Math.floor(max) - Math.ceil(min)
) + Math.ceil(min),
)
);
/**
* Will throttle a method by a fixed ms time, if number is passed.
* if tuple, will throttle by a random time (ms) between each.
* @param milliseconds
* @returns
*/
function throttle(milliseconds: number | [number, number]): any {
let lastCall = 0;
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
const now = Date.now();
if (!lastCall) {
lastCall = now;
return originalMethod.apply(this, args);
}
const ms = Array.isArray(milliseconds)
? getRandomInt(milliseconds[0], milliseconds[1])
: Math.round(milliseconds);
const diff = now - lastCall;
if (diff < ms) {
await asyncDelay(ms - diff);
}
lastCall = Date.now();
return originalMethod.apply(this, args);
};
return descriptor;
};
}
你可以做的:
class MyClass {
@throttle(1000)
async fixedRateLimitedMethod() {
await doSomething()
}
@throttle([1000, 5000])
async randomRateLimitedMethod() {
await doSomething()
}
}
TS游乐场