Rate Limiting: Throttling Consecutive Function Calls with Queues
A common solution for controlling consecutive function calls would be to create a rate limiter or throttled wrapper function. A method that, when given a function and a time delay, will return a new function that when executed consecutively, will invoke the given function parameter with a wait time before it can be executed again.
Below is a working example of an initial design.
As you can see, the boolean value of
isCalled determines whether or not the function has already been called. Now after the given
wait in milliseconds, the
setTimeout callback reverts the
limiter back to a “non-called” state, making the method available for continued execution.
A rather significant limitation to this rate limiter is the other function calls are dropped. For example, if one were to loop over this method, they would lose certain iterations of the call if they occurred within the specified wait period. If the desired intention is for every call to be actualized, this method will not suffice.
A way to improve this would be to queue up each of the calls, so that every iteration will still occur but with the rate limiting timeout between each invocation.
Each execution of the returned function, will push the call to the array variable
calls, which will act as the queue. While the queue contains values, a check will see if the function is already in a called state. If not, it will shift off the top or first method in the queue and call it. Then as with the earlier implementation, the
isCalled boolean value will revert after the allotted timeout.
This does not work.
Why? Because the
setTimeout will never execute until after the
while loop finishes its iterating, which won’t occur until the callback happens, causing an infinite loop.
With this realization or understanding in mind, our real solution lies with good old recursion. By abstracting a
caller method containing the
fn parameter invocation which could be called recursively from within the
setTimeout, it would allow the function execution to occur after the callback and would prevent the unintended continuous iteration.
Now how could the
fn function be supplied with arguments of its own?
Looking into examples of other solutions, one implementation I have found, created by Matthew O’Riordan, contains some insight into ways in which this method could be optimized. His _.rateLimit() method, written as an extension of Underscore.js, adds additional functionality for parameters to be provided at the time of execution.
limiter method’s return function.
Alternatively, if ES6 compatibility is a concern, among the other changes necessary (i.e. ‘let’), an alteration must be made to provide the
arguments to the rate limited function. As shown in the comments, the
arguments object must first be converted into a true array which will enable it to be properly passed into the
apply() method. This method is necessary to allow for the passing of an array of parameters to the
bind() method as opposed to its de facto spread sequence.
limiter function is now a rather straightforward task.
Each message will log in the console at an interval of 500ms. This may easily be replicated with
setInterval(), but its usage gives a rather clear picture of a rate limited function declaration.