I have a Javascript function that calls my backend API using AJAX, let's say, updateDataUsingAjax
. This is a resource intensive process, and I do not wish to allow this function to be invoked too many times in a short time span.
Let's say I intend to allow only 3 requests in every 5 seconds. If the function is called more than that, it should throw an error.
Something similar to what happens if you click the flag button too many times on Stack Overflow (or here):
My initial approach to solving this problem was the following:
Keep track of the execution count and the first instance of function execution.
Execute the function logic if,
Execution count is less than the limit (3 requests),
Or, if more time has passed than the configured time (5 seconds) - in such a case update the timestamp of the first execution as well.
Update the execution count if the function is allowed to execute.
This approach was okay, but I realized that there might be a need to implement such a logic for multiple processes, with different limits. So I decided to write a "throttling function generator", that would take the
- function to be executed,
- maximum request count,
- and the timespan for which the throttling should apply.
It should return a function that can be called just like the target function, except that it would throw an error if the request count has exceeded the limit.
function throttledFunctionGenerator(func, limit, interval) {
var count = 1;
return function(arg) {
// Run the function only if it hasn't yet exceeded the limit.
if (count <= limit) {
// Execute the function.
// Note: Exception handling is not necessary as setTimeout is used.
setTimeout(func, 1, arg);
// If this is the first request, schedule the counter to be reset after specified interval.
if (count === 1) {
setTimeout(function(arg) {
// Reset the counter.
count = 1;
}, interval * 1000, arg);
}
// Increment the count.
count += 1;
} else {
throw "You may perform this action only " + limit + " times in " + interval + " second(s).";
}
};
}
So if I have a function doSomething
that should be called at most 3 times in 10 seconds, I could wrap it using,
var doSomethingThrottled = throttledFunctionGenerator(doSomething, 3, 5);
So, when I intend execute the following code, the fourth invocation should throw an error:
doSomethingThrottled(obj1);
doSomethingThrottled(obj2);
doSomethingThrottled(obj3);
doSomethingThrottled(obj4); // Throws an error.
Here's a demo on Codepen where you can see this in action.
var btn = document.getElementById("btn");
var btn2 = document.getElementById("btn2");
var logArea = document.getElementById("logArea");
function log(message) {
logArea.classList.add("yellow");
logArea.innerText += message + "\n";
setTimeout(function() {
logArea.classList.remove("yellow");
}, 500);
}
function throttledFunctionGenerator(func, limit, interval) {
var count = 1;
return function(arg) {
// Run the function only if it hasn't yet exceeded the limit.
if (count <= limit) {
// Execute the function in next event loop.
// Note: Exception handling is not necessary as setTimeout is used.
setTimeout(func, 1, arg);
// If this is the first request, schedule the counter to be reset after specified interval.
if (count === 1) {
setTimeout(function(arg) {
// Reset the counter.
count = 1;
}, interval * 1000, arg);
}
// Increment the count.
count += 1;
} else {
throw "You may perform this action only " + limit + " times in " + interval + " second(s).";
}
};
}
var thriceEveryFiveSeconds = throttledFunctionGenerator(function(msg) {
log("[Thrice]: " + msg);
}, 3, 5);
var twiceEveryTenSeconds = throttledFunctionGenerator(function(msg) {
log("[Twice]: " + msg);
}, 2, 10);
btn.addEventListener("click", function(event) {
var date = new Date();
thriceEveryFiveSeconds(`Clicked: ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`);
});
btn2.addEventListener("click", function(event) {
var date = new Date();
twiceEveryTenSeconds(`Clicked: ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`);
});
.yellow {
background-color: yellow;
}
<button id="btn">Thrice every 5 seconds</button>
<button id="btn2">Twice every 10 seconds</button>
<pre id="logArea"></pre>
I am aware of the following limitations:
setTimeout
is not precise.The code in its current state only works for functions that take only one argument. But I believe it can be easily extended to work with any number of arguments using the ES6 spread syntax.
Do you see any bugs with the code, or any area of improvement?