I have the following debounce function in typescript:
export function debounce<T>(
callback: (...args: any[]) => void,
wait: number,
context?: T,
immediate?: boolean
) {
let timeout: ReturnType<typeof setTimeout> | null;
return (...args: any[]) => {
const later = () => {
timeout = null;
if (!immediate) {
callback.apply(context, args);
}
};
const callNow = immediate && !timeout;
if (typeof timeout === "number") {
clearTimeout(timeout);
}
timeout = setTimeout(later, wait);
if (callNow) {
callback.apply(context, args);
}
};
}
i'm looking for a better way to cast
...args: any[]
with a safer type.
How can i change it?
UPDATE
I came out with this solution:
export function debounce<T = unknown, R = void>(
callback: (...args: unknown[]) => R,
wait: number,
context?: T,
immediate?: boolean
) {
What do you think?
I made some changes.
First, we want the generic type to be a function so that later we can safely get the parameters with the Paramters
utility type. Second, personally I like to have the wait time first because I frequently apply the same debounce timing to a lot of listeners with partial application, YMMV. Third, we want to return a regular (non-arrow) function with a typed this
parameter so that the caller doesn't need to explicitly pass in a context.
// NOTE: the any[] here is still type-safe: we're just
// constraining the generic to be a function type and the
// concrete type of T will be determined at the call site
function debounce<T extends (...args: any[]) => void>(
wait: number,
callback: T,
immediate = false,
) {
// This is a number in the browser and an object in Node.js,
// so we'll use the ReturnType utility to cover both cases.
let timeout: ReturnType<typeof setTimeout> | null;
return function <U>(this: U, ...args: Parameters<typeof callback>) {
const context = this;
const later = () => {
timeout = null;
if (!immediate) {
callback.apply(context, args);
}
};
const callNow = immediate && !timeout;
if (typeof timeout === "number") {
clearTimeout(timeout);
}
timeout = setTimeout(later, wait);
if (callNow) {
callback.apply(context, args);
}
};
}
For usage we can use for both plain functions and methods:
const handler: (evt: Event) => void = debounce(500, (evt: Event) => console.log(evt.target));
class Foo {
constructor () {
// can also type-safely decorate methods
this.bar = debounce(500, this.bar.bind(this));
}
bar (evt: Event): void {
console.log(evt.target);
}
}
Even on the methods of object literals:
interface Bar {
a: number,
f: () => void
}
const bar: Bar = {
a: 1,
f: debounce<(this: Bar) => void>(100, function() { console.log(this.a); }),
}