限制javascript函数

时间:2015-11-26 20:03:45

标签: javascript rate-limiting

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

编辑:请执行 downvote答案而不留下评论解释为什么答案不好。

编辑2:一个好的解决方案不会强行将每个调用间隔100毫秒。这使得10次调用的最小运行时间为1秒 - 实际上你可以实现这些(几乎)同时并且可能在几分之一秒内完成。

7 个答案:

答案 0 :(得分:9)

此处提出的大多数其他解决方案均使用间隔或递归函数均匀地隔离函数调用。

对你的问题的这种解释并没有真正做到我认为你所要求的,因为它要求你以固定的间隔调用该函数。

如果要限制函数调用的次数,而不管函数调用之间的空间如何,可以使用以下方法。

定义一个工厂函数来保存当前时间,计数和队列然后返回一个函数,该函数根据最后记录的当前时间和计数检查当前时间,然后执行队列中的第一项,或等到下一秒再试一次。

将回调函数传递给工厂函数创建的函数。回调函数将进入队列。 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);
        });
    });
}
&#13;
&#13;
&#13;

答案 1 :(得分: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

答案 2 :(得分:1)

递归版本总是看起来更酷

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

答案 3 :(得分:0)

您可以使用值{100(1000毫秒/ 10)的setTimeout将输出限制为每秒10次。使用变量call来计算呼叫数。如果您想在其他地方调用相同的功能,请记得将计数器call重置为1,以便重新开始:

&#13;
&#13;
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);
});
&#13;
<div id="test"/>
&#13;
&#13;
&#13;

答案 4 :(得分:0)

这是一个带回调的递归版本(这是你的意思&#34;当有新的点可用时继续执行&#34;?)

编辑:它现在更加抽象 - 如果你想看到原始实现(非常具体),请参阅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!";
});

这是一个小伙伴:http://jsfiddle.net/52wq9vLf/2/

答案 5 :(得分:0)

在我拥有的时间里,这是我能想到的最好的。

请注意,由于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);

答案 6 :(得分:0)

这些都不是真正有用的。我不是一个很好的开发人员,我正在整理一个测试并编写了自己的少量函数。我不明白被接受的答案在做什么,所以也许这会对其他人有所帮助。

应该相当可读。

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

想象一下,您的办公桌上有一个托盘,人们可以将任务放在一个收件箱中。同事添加任务的速度比执行任务快,因此您需要制定一个计划。您总是从堆栈的底部开始,而收件箱为空时,您可以停止寻找下一个内容。这就是全部。