[英]How can I rate limit how fast a javascript function allows itself to be called?
[英]Rate limit a javascript function
如何将函数限制为每秒仅运行 10 次,但在新的“点”可用时继续执行? 这意味着我们会尽快调用该函数 10 次,并且在任何函数调用后经过 1 秒后,我们可以再调用一次。
这个描述可能会令人困惑——但答案将是完成 X 次 API 调用的最快方法,给定速率限制。
示例:这是一个循环遍历字母表以打印每个字母的示例。 我们如何将其限制为每秒仅printLetter
10 次? 我仍然想以适当的速度遍历所有字母。
function printLetter(letter){
console.log(letter);
}
var alphabet = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "X", "Y", "Z"];
// How can I limit this to only run 10 times per second, still loop through every letter, and complete as fast as possible (i.e. not add a hard spacing of 100ms)?
alphabet.forEach(function(letter){
printLetter(letter);
});
一个好的解决方案不会强行将每个调用间隔 100 毫秒。 这使得 10 次调用的最短运行时间为 1 秒 - 实际上您可以(几乎)同时执行这些操作并可能在几分之一秒内完成。
此处提出的大多数其他解决方案使用间隔或带有超时的递归函数均匀地间隔函数调用。
对您的问题的这种解释并没有真正满足我认为您的要求,因为它要求您以设定的时间间隔调用该函数。
如果你想限制一个函数可以被调用的次数,而不管函数调用之间的空间有多大,你可以使用下面的方法。
定义一个工厂函数来保存当前时间、计数和队列,然后返回一个函数,该函数根据最后记录的当前时间检查当前时间,然后计数要么执行队列中的第一项,要么等到下一秒再试一次.
将回调函数传递给工厂函数创建的函数。 回调函数将进入队列。 limit 函数执行队列中的前 10 个函数,然后等待此间隔结束执行接下来的 10 个函数,直到队列为空。
从工厂函数返回限制函数。
var factory = function(){ var time = 0, count = 0, difference = 0, queue = []; return function limit(func){ if(func) queue.push(func); difference = 1000 - (window.performance.now() - time); if(difference <= 0) { time = window.performance.now(); count = 0; } if(++count <= 10) (queue.shift())(); else setTimeout(limit, difference); }; }; var limited = factory(); var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""); // This is to show a separator when waiting. var prevDate = window.performance.now(), difference; // This ends up as 2600 function calls, // all executed in the order in which they were queued. for(var i = 0; i < 100; ++i) { alphabet.forEach(function(letter) { limited(function(){ /** This is to show a separator when waiting. **/ difference = window.performance.now() - prevDate; prevDate = window.performance.now(); if(difference > 100) console.log('wait'); /***********************************************/ console.log(letter); }); }); }
你必须做的有点不同:
var alphabet = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "X", "Y", "Z"];
function printLetter(letterId) {
if (letterId < alphabet.length) { // avoid index out of bounds
console.log(alphabet[letterId]);
var nextId = letterId + 1
if (nextId < alphabet.length) // if there is a next letter print it in 10 seconds
setTimeout("printLetter(" + nextId + ")", 10000/*milliseconds*/);
}
}
printLetter(0); // start at the first letter
演示:
var alphabet = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "X", "Y", "Z"]; function printLetter(letterId) { if (letterId < alphabet.length) { // avoid index out of bounds console.log(alphabet[letterId]); document.body.innerHTML += "<br />" + alphabet[letterId]; // for ***DEMO*** only var nextId = letterId + 1 if (nextId < alphabet.length) // if there is a next letter print it in 10 seconds setTimeout("printLetter(" + nextId + ")", 100 /*milliseconds*/ ); // one second for ***DEMO*** only } } printLetter(0); // start at the first letter
递归版本总是看起来更酷
// Print the first letter, wait, and do it again on a sub array until array == []
// All wrapped up in a self-invoking function
var alphabet = ...
var ms = 100 // 10 letters per seconds
(function printSlowly( array, speed ){
if( array.length == 0 ) return;
setTimeout(function(){
console.log( array[0] );
printSlowly( array.slice(1), speed );
}, speed );
})( alphabet, ms);
您可以使用值为 100(即 1000 毫秒/10)的setTimeout
将输出限制为每秒 10 次。 使用变量call
来计算调用次数。 如果你想在其他地方调用相同的函数,记得将计数器call
重置为 1,这样你就可以重新开始:
var call = 1; function printLetter(letter){ call++; var x = call * 100; //alert(x); setTimeout(function(){ document.getElementById("test").innerHTML += letter; }, x); } var alphabet = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "X", "Y", "Z"]; // How can I limit this to only run 10 times per second, but still loop through every letter? alphabet.forEach(function(letter){ printLetter(letter); });
<div id="test"/>
这是一个带有回调的递归版本(这就是您所说的“在有新点可用时继续执行”的意思吗?)
编辑:现在更抽象了 - 如果你想查看原始实现(非常具体),请参阅http://jsfiddle.net/52wq9vLf/0/
var alphabet = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "X", "Y", "Z"];
/**
* @function printLetter
* @param {Array} array The array to iterate over
* @param {Function} iterateFunc The function called on each item
* @param {Number} start The index of the array to start on
* @param {Number} speed The time (in milliseconds) between each iteration
* @param {Function} done The callback function to be run on finishing
*/
function slowArrayIterate(array, iterateFunc, start, speed, done) {
// Native array functions use these three params, so just maintaining the standard
iterateFunc(array[start], start, array);
if (typeof array[start + 1] !== 'undefined') {
setTimeout(function() {
slowArrayIterate(array, iterateFunc, start + 1, speed, done);
}, speed);
} else {
done();
}
};
slowArrayIterate(alphabet, function(arrayItem) {
document.getElementById("letters").innerHTML += arrayItem;
}, 0, 100, function() {
// stuff to do when finished
document.getElementById("letters").innerHTML += " - done!";
});
这是一个 jsfiddle: http : //jsfiddle.net/52wq9vLf/2/
这是我能想到的最好的时间。
请注意,由于胖箭头函数的实现存在错误,因此在 Firefox v43 下无法正确运行任何内容。
var MAX_RUNS_PER_WINDOW = 10; var RUN_WINDOW = 1000; function limit(fn) { var callQueue = [], invokeTimes = Object.create(circularQueue), waitId = null; function limited() { callQueue.push(() => { invokeTimes.unshift(performance.now()) fn.apply(this, arguments); }); if (mayProceed()) { return dequeue(); } if (waitId === null) { waitId = setTimeout(dequeue, timeToWait()); } } limited.cancel = function() { clearTimeout(waitId); }; return limited; function dequeue() { waitId = null ; clearTimeout(waitId); callQueue.shift()(); if (mayProceed()) { return dequeue(); } if (callQueue.length) { waitId = setTimeout(dequeue, timeToWait()); } } function mayProceed() { return callQueue.length && (timeForMaxRuns() >= RUN_WINDOW); } function timeToWait() { var ttw = RUN_WINDOW - timeForMaxRuns(); return ttw < 0 ? 0 : ttw; } function timeForMaxRuns() { return (performance.now() - (invokeTimes[MAX_RUNS_PER_WINDOW - 1] || 0)); } } var circularQueue = []; var originalUnshift = circularQueue.unshift; circularQueue.MAX_LENGTH = MAX_RUNS_PER_WINDOW; circularQueue.unshift = function(element) { if (this.length === this.MAX_LENGTH) { this.pop(); } return originalUnshift.call(this, element); } var printLetter = limit(function(letter) { document.write(letter); }); ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'X', 'Y', 'Z'].forEach(printLetter);
这些都没有多大用处。 我不是一个很好的开发人员,我正在编写一个测试并编写我自己的一小部分函数。 我无法理解接受的答案在做什么,所以也许这会帮助其他人。
应该是相当可读的。
var queue = [];
var queueInterval;
var queueCallsPerSecond = 5;
function addToQueue(callback, args) {
//push this callback to the end of the line.
queue.push({
callback: callback,
args: args
});
//if queueInterval isn't running, set it up to run
if(!queueInterval){
//first one happens right away
var nextQueue = queue.shift();
nextQueue.callback(...nextQueue.args);
queueInterval = setInterval(function(){
//if queue is empty clear the interval
if(queue.length === 0) {
clearInterval(queueInterval);
return false;
}
//take one off, run it
nextQueue = queue.shift();
nextQueue.callback(...nextQueue.args);
}, 1000 / queueCallsPerSecond);
}
}
//implementation addToQueue(callback, arguments to send to the callback when it's time to go) - in this case I'm passing 'i' to an anonymous function.
for(var i = 0; i < 20; i++){
addToQueue(
function(num) {
console.log(num);
},
[i]
);
}
想象一下,您的办公桌上有一个托盘,人们将任务放入……收件箱。 同事添加任务的速度比您执行它们的速度要快,因此您需要制定计划。 您总是从堆栈的底部取走,当收件箱为空时,您可以停止寻找下一步。 这就是它所做的一切。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.