Rate Limiting: Throttling Consecutive Function Calls with Queues
An implementation for limiting the amount of time between function calls in JavaScript may be necessary when any sort of process requires regulation like generating network requests. Whether it’s an API call, data store, or even a DOM event, multiple consecutive firings of a function may not give enough time for the intended result before the next invocation occurs.
Rate Limiters
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.
Dropped Calls
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 while
loop blocks the event-driven nature of JavaScript and its event loop. Our callback in 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.
Recursive Design
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.
Passing Arguments
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.
Continuing with the usage of idiomatic JavaScript and not requiring any dependencies or additional libraries is the most pragmatic approach for this implementation. Now making sure that arguments may be passed to the rate limited function requires their inclusion within the limiter
method’s return function.
For simplicity the Spread operator is used on the Arguments object to bind the arguments
to our fn
parameter.
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.
Brief Implementation
Implementing the 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.
Rate limiters and throttling functions exist through a plethora of JavaScript libraries; I’m now looking forward to comparing their methodologies with the one above.