TypeScript refuses to compile the debounce
function because something wrong with type of the wrapping function:
export function debounce<F extends ((...args: any[]) => void)>(fn: F, timeout: number): F {
let timer: NodeJS.Timeout | undefined
// Problem here, TypeScript complains it's not the same function as F
return ((...args: any[]) => {
if (timer) clearTimeout(timer)
timer = setTimeout(() => fn(...args), timeout)
})
}
The error:
Type '(...args: any[]) => void' is not assignable to type 'F'.
'(...args: any[]) => void' is assignable to the constraint of type 'F', but 'F' could be instantiated with a different subtype of constraint '(...args: any[]) => void'.ts(2322)
How to fix that? Without forced typecasting return ... as F
or return ... as any
The problem is that the constraint (...args: any[]) => void
on F
can be satisified by a number of types that you might find surprising, and the function you're returning will not be assignable to such types. For example:
debounce(() => "oopsie", 1000)().toUpperCase(); // okay at compile time, typeError at runtime
Here, the function type F
returns a string
value; this is assignable to a void
-returning function type, as explained in the FAQ. But of course debounce()
will not return a string
-returning function, so the return type of debounce()
is not the same F
as passed in.
Also:
function foo() { };
foo.prop = 123; // property declaration
debounce(foo, 1000).prop.toFixed(); // okay at compile time, TypeError at runtime
In this case, we have a function with a property declared on it. So the F
type here will be a function type ()=>void
with an extra prop
property. But again, debounce()
will not return a function with this extra prop
property on it, so the return type of debounce()
is again not the same F
as passed in.
The fix here is to make debounce()
only generic enough to represent what you're actually doing. The returned function will take the same argument list as the passed-in function, so we need the argument list to be generic. And the returned function will definitely return void
and is not going to have extra properties on it. So only the argument list needs a type parameter (say, A
), and both the input and output functions will be of the type (...args: A) => void
:
export function debounce<A extends any[]>(
fn: (...args: A) => void,
timeout: number
): (...args: A) => void {
let timer: NodeJS.Timeout | undefined
return ((...args: A) => {
if (timer) clearTimeout(timer)
timer = setTimeout(() => fn(...args), timeout)
})
}
That compiles with no error. Okay, hope that helps; good luck!
Link to code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With