I have an utility functions in TypeScript, named throttle
, that I frequently use for controlling the execution rate of functions. However, I would like to extend these functions to support leading
options.
Leading:
leading
is set to true
, the function will be executed immediately on the first call. This means the function is triggered at the beginning of the wait period. For example, if you are throttling a function on a scroll event with a leading
option, the function will be called as soon as the scroll starts.These options allow developers to fine-tune control over function execution timing, accommodating specific use cases in their applications.
Here is the current implementation of the throttle
function:
export function throttle(fn: (...args: any[]) => void, wait = 100) {
let timeoutId: ReturnType<typeof setTimeout> | undefined;
let lastTime = Date.now();
const execute = (...args: any[]) => {
lastTime = Date.now();
fn(...args);
};
return (...args: any[]) => {
const currentTime = Date.now();
const elapsed = currentTime - lastTime;
if (elapsed >= wait) {
// If enough time has passed since the last call, execute the function immediately
execute(...args);
} else {
// If not enough time has passed, schedule the function call after the remaining delay
if (timeoutId !== undefined) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => {
execute(...args);
timeoutId = undefined;
}, wait - elapsed);
}
};
}
How can I modify the throttle
function to include options for leading
calls?
I would appreciate any example code or explanations on how to implement these options effectively. Thank you!
The following code is for test:
const sleep = (wait: number) => new Promise((r) => setTimeout(r, wait));
(async () => {
const wait = 100;
const fn = ((...args: any[]) => console.log(args));
const throttled = throttle(fn, wait);
throttled('a', 'b', 'c'); // This should be called
await sleep(wait - 5);
throttled('b', 'a', 'c');
await sleep(wait - 10);
throttled('c', 'b', 'a'); // This should be called too
await sleep(wait + 10); // Ensure we've waited long enough for it to be called twice
})
All you need to do to get leading
to work is to initialise lastTime
to -Infinity
, so that the time elapsed on the first call of throttle
from lastTime
to currentTime
will be Infinity
and thus definitely greater than wait
(assuming wait
is some finite time):
function throttle(fn: (...args: any[]) => void, wait = 100, { leading = false } = {}) {
// ⋯
let lastTime = leading ? -Infinity : Date.now();
// ⋯ same as existing version
}
I've added leading
as a destructured function parameter which defaults to false
and then set the entire options object to default to the empty object. So if you call throttle(fn, wait)
it will behave like throttle(fn, wait, {})
, which behaves like throttle(fn, wait, {leading: false})
. If you want leading
to be true
then you should call throttle(fn, wait, {leading: true})
.
Note also that since wait
has a default argument, if you want to set options but not wait
, you'll need to pass undefined
in explicitly like throttle(fn, undefined, {leading: true})
. Personally I'd say it's much cleaner to add wait
to the options object like { wait = 100, leading = false }
and then call thottle(fn, {wait: 1234})
or throttle(fn, {leading: true})
. But that's a digression from the question as asked.