简体   繁体   English

速率限制一个javascript函数

[英]Rate limit a javascript function

How can I limit a function to only run 10 times per second, but continue execution when new "spots" are available?如何将函数限制为每秒仅运行 10 次,但在新的“点”可用时继续执行? This means we'd call the function 10 times as soon as possible, and when 1 second has elapsed since any function call we can do another call.这意味着我们会尽快调用该函数 10 次,并且在任何函数调用后经过 1 秒后,我们可以再调用一次。

This description may be confusing - but the answer will be the fastest way to complete X number of API calls, given a rate limit.这个描述可能会令人困惑——但答案将是完成 X 次 API 调用的最快方法,给定速率限制。

Example: Here is an example that loops through the alphabet to print each letter.示例:这是一个循环遍历字母表以打印每个字母的示例。 How can we limit this to only printLetter 10 times per second?我们如何将其限制为每秒仅printLetter 10 次? I still want to loop through all letters, just at the appropriate rate.我仍然想以适当的速度遍历所有字母。

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);
});

A good solution will not forcefully space out each call by 100ms.一个好的解决方案不会强行将每个调用间隔 100 毫秒。 This makes the minimum run time 1second for 10 calls - when you could in fact do these (nearly) simultaneously and potentially complete in a fraction of a second.这使得 10 次调用的最短运行时间为 1 秒 - 实际上您可以(几乎)同时执行这些操作并可能在几分之一秒内完成。

Most of the other proposed solutions here evenly space the function calls using an interval or recursive function with a timeout.此处提出的大多数其他解决方案使用间隔或带有超时的递归函数均匀地间隔函数调用。

This interpretation of your question doesn't really do what I believe you're asking for, because it requires you to call the function at a set interval.对您的问题的这种解释并没有真正满足我认为您的要求,因为它要求您以设定的时间间隔调用该函数。

If you would like to limit how many times a function can be called regardless of the space between the function calls, you can use the following method.如果你想限制一个函数可以被调用的次数,而不管函数调用之间的空间有多大,你可以使用下面的方法。


Define a factory function to hold the current time, count and queue then return a function which checks the current time against the last recorded current time and the count then either executes the first item in the queue, or waits until the next second to try again.定义一个工厂函数来保存当前时间、计数和队列,然后返回一个函数,该函数根据最后记录的当前时间检查当前时间,然后计数要么执行队列中的第一项,要么等到下一秒再试一次.

Pass a callback function to the function created by the factory function.将回调函数传递给工厂函数创建的函数。 The callback function will be entered into a queue.回调函数将进入队列。 The limit function executes the first 10 functions in the queue and then waits until this interval has finished to execute the next 10 functions until the queue is empty. limit 函数执行队列中的前 10 个函数,然后等待此间隔结束执行接下来的 10 个函数,直到队列为空。

Return the limit function from the factory function.从工厂函数返回限制函数。

 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); }); }); }

You have to do it a little different:你必须做的有点不同:

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

Demo:演示:

 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

Recursive version always looks cooler递归版本总是看起来更酷

// 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);

You can use setTimeout with the value 100 (which is a 1000 milliseconds / 10) to limit the output to 10 times per second.您可以使用值为 100(即 1000 毫秒/10)的setTimeout将输出限制为每秒 10 次。 Use a variable call to count the calls.使用变量call来计算调用次数。 If you want to call the same function in other places, remember to reset the counter call to 1, so you start fresh:如果你想在其他地方调用相同的函数,记得将计数器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"/>

Here's a recursive version with a callback (Is that what you mean by "continue execution when new spots are available"?)这是一个带有回调的递归版本(这就是您所说的“在有新点可用时继续执行”的意思吗?)

EDIT: It's even more abstracted now - if you want to see the original implementation (very specific) see http://jsfiddle.net/52wq9vLf/0/编辑:现在更抽象了 - 如果你想查看原始实现(非常具体),请参阅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!";
});

Here's a jsfiddle: http://jsfiddle.net/52wq9vLf/2/这是一个 jsfiddle: http : //jsfiddle.net/52wq9vLf/2/

This is the best I could come up with in the time I had.这是我能想到的最好的时间。

Note that this will not run correctly anything under Firefox v43 due to a bug in its implementation of fat-arrow functions.请注意,由于胖箭头函数的实现存在错误,因此在 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 of these were really much use.这些都没有多大用处。 I'm not a very good developer, and I'm whipping together a test and wrote my own little set of functions.我不是一个很好的开发人员,我正在编写一个测试并编写我自己的一小部分函数。 I couldn't understand what the accepted answer was doing, so maybe this will help someone else.我无法理解接受的答案在做什么,所以也许这会帮助其他人。

Should be fairly readable.应该是相当可读的。

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]
    );
}

Imagine you have a tray on your desk that people put tasks in... an inbox.想象一下,您的办公桌上有一个托盘,人们将任务放入……收件箱。 Tasks get added by your coworkers faster than you can execute them, so you need to come up with a plan.同事添加任务的速度比您执行它们的速度要快,因此您需要制定计划。 You always take from the bottom of the stack and when the inbox is empty you can stop looking for what's next.您总是从堆栈的底部取走,当收件箱为空时,您可以停止寻找下一步。 That's all it does.这就是它所做的一切。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM