速率限制 javascript 函数
Rate limit a javascript function
如何将函数限制为每秒仅 运行 10 次,但在新的 "spots" 可用时继续执行?这意味着我们将尽快调用该函数 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);
});
一个好的解决方案不会强制 space 结束每个调用 100 毫秒。这使得 10 次调用的最短 运行 时间为 1 秒 - 实际上您可以(几乎)同时执行这些操作并且可能在几分之一秒内完成。
你必须做一些不同的事情:
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"/>
这是一个带有回调的递归版本(你说的 "continue execution when new spots are available" 是这个意思吗?)
编辑:现在更抽象了 - 如果您想查看原始实现(非常具体),请参阅 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/
此处提出的大多数其他解决方案均匀地 space 函数调用使用具有超时的间隔或递归函数。
我认为您对问题的这种解释并没有真正满足您的要求,因为它要求您以设定的时间间隔调用该函数。
如果您想限制一个函数可以被调用的次数而不考虑函数调用之间的space,您可以使用以下方法。
定义一个工厂函数来保存当前时间、计数和排队,然后 return 一个函数检查当前时间与最后记录的当前时间和计数,然后执行队列中的第一个项目,或等到下一秒再试。
将回调函数传递给工厂函数创建的函数。回调函数将进入队列。 limit 函数执行队列中的前 10 个函数,然后等待此间隔完成后执行接下来的 10 个函数,直到队列为空。
Return工厂函数的限制函数。
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);
});
});
}
这是我有时间能想到的最好的。
请注意,由于 a bug 在其实现的粗箭头功能中,这不会 运行 在 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);
None这几个用处真大。我不是一个非常好的开发人员,我正在一起进行测试并编写我自己的一小部分函数。我无法理解已接受的答案在做什么,所以也许这会对其他人有所帮助。
应该是相当可读的。
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]
);
}
假设您的办公桌上有一个托盘,人们可以将任务放入……收件箱。同事添加任务的速度比您执行任务的速度快,因此您需要制定一个计划。您总是从堆栈的底部获取,当收件箱为空时,您可以停止寻找下一个。仅此而已。
如何将函数限制为每秒仅 运行 10 次,但在新的 "spots" 可用时继续执行?这意味着我们将尽快调用该函数 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);
});
一个好的解决方案不会强制 space 结束每个调用 100 毫秒。这使得 10 次调用的最短 运行 时间为 1 秒 - 实际上您可以(几乎)同时执行这些操作并且可能在几分之一秒内完成。
你必须做一些不同的事情:
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"/>
这是一个带有回调的递归版本(你说的 "continue execution when new spots are available" 是这个意思吗?)
编辑:现在更抽象了 - 如果您想查看原始实现(非常具体),请参阅 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/
此处提出的大多数其他解决方案均匀地 space 函数调用使用具有超时的间隔或递归函数。
我认为您对问题的这种解释并没有真正满足您的要求,因为它要求您以设定的时间间隔调用该函数。
如果您想限制一个函数可以被调用的次数而不考虑函数调用之间的space,您可以使用以下方法。
定义一个工厂函数来保存当前时间、计数和排队,然后 return 一个函数检查当前时间与最后记录的当前时间和计数,然后执行队列中的第一个项目,或等到下一秒再试。
将回调函数传递给工厂函数创建的函数。回调函数将进入队列。 limit 函数执行队列中的前 10 个函数,然后等待此间隔完成后执行接下来的 10 个函数,直到队列为空。
Return工厂函数的限制函数。
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);
});
});
}
这是我有时间能想到的最好的。
请注意,由于 a bug 在其实现的粗箭头功能中,这不会 运行 在 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);
None这几个用处真大。我不是一个非常好的开发人员,我正在一起进行测试并编写我自己的一小部分函数。我无法理解已接受的答案在做什么,所以也许这会对其他人有所帮助。
应该是相当可读的。
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]
);
}
假设您的办公桌上有一个托盘,人们可以将任务放入……收件箱。同事添加任务的速度比您执行任务的速度快,因此您需要制定一个计划。您总是从堆栈的底部获取,当收件箱为空时,您可以停止寻找下一个。仅此而已。