- I needed to have a lightweight throttle function that I can rely on.
- Calls ASAP
- Defers call if throttled
- Can cancel an enqueued call if you really want to (say, a component has unmounted, instance was destroyed etc.)
- Something TSC would infer type safely
/**
* Throttles function & keep the last call.
*
* @param thunk a function that should be throttled
* @param delay ms
* @returns function
*/
export const throttle = <T>(thunk: T, delay: number): T & { cancel(): void } => {
if (typeof thunk !== 'function') throw new Error('thunk must be a function');
let ok = true;
let timer: number;
const pending = { current: null as unknown[] | null };
const throttled: T = ((...args: unknown[]) => {
if (!ok) {
// timer is ticking, update arguments
pending.current = args;
} else {
ok = false;
thunk(...args);
pending.current = null;
timer && self.clearTimeout(timer);
timer = self.setTimeout(() => {
ok = true;
if (pending.current) {
// arguments queued!
if (typeof throttled !== 'function') throw new Error('throttled must be a function');
throttled(...pending.current);
}
}, delay);
}
}) as unknown as T;
(throttled as T & { cancel(): void }).cancel = () => {
timer && clearTimeout(timer);
pending.current = null;
};
// ts cannot infer the return type correctly
return throttled as unknown as T & { cancel(): void };
};
const run = throttle((msg: string) => console.log(msg), 1000);
run('hej1'); // immediately fired
await sleep(200);
run('hej2'); // discarded (superseded by 3)
await sleep(200);
run('hej3'); // fired after 1s
await sleep(500);
run('hej4'); // discarded
await sleep(500);
run('hej5'); // fired after 2s (unless you don't cancel it)
run.cancel(); // << comment this to prevent '5' firing on the next tick
console.log('>>>test---');
- WTFPL
- Do wtf you wish to do