Reputation: 63
I need a function to have certain amount of time between one function call and another, very similar to what a throttle would do, but I need all the function calls to be attended.
Let me explain:
Let's imagine we have a throttle of 10 seconds on a function foo
, and an event that calls foobar.
The event is fired 3 times, one every 5 seconds. Only the first one and the last one would be attended while the one in the middle would be ignored.
Normal throttle:
[event]--5s--[event]--5s--[event]
[foo]---------10s---------[foo]
What I need is that the event is always listened, but 10 seconds after the previous one.
[event]--5s--[event]--5s--[event]
[foo]----------10s--------[foo]---------10s--------[foo]
Any ideas? I thought about throttle and also debouncer but none of them would fit what I need to do.
Upvotes: 2
Views: 1027
Reputation: 854
I solved it like this. It's basically a normal throttle, but it keeps track when the function is called while it's in cooldown and keeps executing it on schedule as long as that's happening.
function throttle(expensiveFunction, delay = 1000) {
let cooldown = false;
let calledDuringCooldown = false;
return function () {
if (cooldown) {
// Function call suppressed, make note
calledDuringCooldown = true;
} else {
// Function is run and enters cooldown
cooldown = true;
expensiveFunction();
// Attempt to remove cooldown after the delay
setTimeout(function nextTick() {
if (calledDuringCooldown) {
calledDuringCooldown = false;
// Call the function again and stay in cooldown
expensiveFunction();
setTimeout(nextTick, delay);
} else {
// Function was not called again,
// so remove the cooldown
cooldown = false;
}
}, delay);
}
};
}
// Example usage
function timid() {
console.log(performance.now(), "I don't want to be called very often...");
}
const throttled = throttle(timid, 2000);
// Should be called only twice, with a 2s delay
throttled();
throttled();
throttled();
Upvotes: 1
Reputation: 63
In case anyone's interested, I used Alex Wayne's answer and added a bit of logic to have a better control on the interval as he mentioned.
const queue = []
var intervalExecution
function rateLimit(fn) {
queue.push(fn)
if (queue.length && !intervalExecution) {
intervalExecution = setInterval(() => {
const nextFn = queue.shift()
if (nextFn) {
nextFn()
} else {
clearInterval(intervalExecution)
intervalExecution = undefined
}
}, 1000)
}
}
rateLimit(() => console.log('test'))
rateLimit(() => console.log('test'))
rateLimit(() => console.log('test'))
rateLimit(() => console.log('test'))
Upvotes: 1
Reputation: 187262
This is pretty simple to do. Just create an array of function objects, and pull the oldest one off every 10 seconds and execute it.
const queue = []
function rateLimit(fn) {
queue.push(fn)
}
setInterval(() => {
const nextFn = queue.shift() // remove the first function, and return it.
if (nextFn) nextFn() // execute the first function if it exists.
}, 1000) // Your interval in milliseconds. Set to 1s for testing.
rateLimit(() => console.log('A'))
rateLimit(() => console.log('B'))
rateLimit(() => console.log('C'))
You could add some logic to clear the interval when the queue is empty, and start it up again fresh when a new item comes in so that the first call always happens immediately if it's not waiting. But I leave that an exercise for you.
Upvotes: 2