对javascript函数进行速率限制



如何将函数限制为每秒仅运行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);
});

一个好的解决方案不会强制将每个调用间隔100ms。这使得10个调用的最短运行时间为1秒,而实际上您可以(几乎)同时执行这些调用,并且可能在几分之一秒内完成。

这里提出的大多数其他解决方案都使用间隔或带超时的递归函数来均匀地分配函数调用的空间。

对你的问题的这种解释并没有真正达到我相信你的要求,因为它要求你以设定的间隔调用函数。

如果您想限制函数的调用次数,而不考虑函数调用之间的间隔,可以使用以下方法。


定义一个工厂函数来保存当前时间、计数和队列,然后返回一个函数,该函数将当前时间与上次记录的当前时间进行比较,然后计数执行队列中的第一项,或者等待下一秒重试。

将回调函数传递给工厂函数创建的函数。回调函数将被输入到队列中。极限函数执行队列中的前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]
    );
}

想象一下,你的桌子上有一个托盘,人们把任务放在收件箱里。你的同事添加任务的速度比你执行任务的速度快,所以你需要制定一个计划。你总是从收件箱的底部开始,当收件箱是空的时,你可以停止寻找下一个。这就是它所做的一切。

最新更新