'How to create a function with a typed callback which ensures all callback parameters are passed to it
Given a function that accepts a callback with typed arguments, how can I make sure it doesn't allow functions without arguments.
I have added a sample code snippet that represents my problem
function x(cb: (a: number) => any) {
cb(12)
}
x((a: number) => 1) // OK
x(() => 1) // OK
x((a: string) => 1) // Error: Argument of type '(x: string) => number' is not assignable to parameter of type '(a: number) => any'.
The second line should generate an error as this doesn't pass a complete list of parameters required by the callback
I want to enforce that every usage of function x
should have all parameters in the callback as specified in the original function signature
Solution 1:[1]
The general behavior of TypeScript is that it's okay to have a function which requires less arguments than what's provided. For example, when you have an onClick
function (e: MouseEvent) => void
it's fine to provide a function which does not use e
.
So we have to get tricky here in order to disallow () => 1
as this function is assignable to (a: number) => any
.
Solution
function x<T extends (a: number) => any>(cb: T & ([] extends Parameters<T> ? never : T)) {
cb(12)
}
x((a: number) => 1) // OK
x(() => 1) // Error: Argument of type '() => number' is not assignable to parameter of type 'never'.
x((a: string) => 1) // Error: Argument of type '(x: string) => number' is not assignable to parameter of type '(a: number) => any'.
Explanation
I am using a generic type parameter T
to represent the callback argument of our function x
. We say that cb
must be T
, but we also impose an additional rule. We need to check the arguments of the callback which are Parameters<T>
. If an empty array []
is assignable to these arguments ([] extends Parameters<T>
), we say that cb
must be T & never
.
T & never
is an impossible condition which causes us to get the red-underlined error on your second example. Here T
is inferred as () => number
so cb
must be never
. Therefore we get an error "Argument of type '() => number' is not assignable to parameter of type 'never'."
Your example function x
doesn't return anything, but it could return cb(12)
and get the correct return type based on the return type of the callback cb
.
function x<T extends (a: number) => any>(cb: T & ([] extends Parameters<T> ? never : T)): ReturnType<T> {
return cb(12)
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Linda Paiste |