Looking for a way to define a function in TypeScript that will accept, as one of it's arguments, a function that may be of different signatures
const foo = (a: string, b: number) => {
//do something
}
const bar = (a: number, b: string, c: boolean) => {
//do something
}
const myDynamicFunction = (
str: string,
// I tried this, but no dice
method: ((a: string, b: number) => void) | ((a: number, b: string, c: boolean) => void),
num: number,
bool?: boolean
) => {
if (bool) {
method(num, str, bool)
} else {
method(str, num)
}
}
myDynamicFunction(
'string', foo, 5
)
myDynamicFunction(
'string', bar, 5, true
)
I tried going down the "function overloads" rabbit hole, but that just left me where I started w/ essentially the same error of unexpected number of arguments.
I think you'd have to cast your method, because TypeScript cant figure this one out:
if (bool) {
(method as (a: number, b: string, c: boolean) => void)(num, str, bool)
} else {
(method as (a: string, b: number) => void)(str, num)
}
There were some subtleties that I couldn't get my head around at first, but breaking it down finally got me there. Typescript seems to have some difficulty disambiguating parameter types without first defining discreet type aliases.
We need to use a type predicate to determine, based on the value of bool, whether we are dealing with a call to a BAR like function.
When we do that, we still haven't narrowed the type of bool to be specifically boolean (another predicate would do the job also), but we know that's the case so we can use the ! non-null assertion to allow it to be passed to a method that requires boolean.
type FOO = (a: string, b: number) => void
type BAR = (a: number, b: string, c: boolean) => void
function isBAR(method: any, bool: boolean | undefined): method is BAR {
return typeof method === 'function' && bool !== undefined
}
const foo = (a: string, b: number) => {
//do something
}
const bar = (a: number, b: string, c: boolean) => {
//do something
}
const myDynamicFunction = (
str: string,
method: FOO | BAR,
num: number,
bool?: boolean
) => {
isBAR(method, bool) ? method(num, str, bool!) : method(str, num)
}
myDynamicFunction('string', foo, 5)
myDynamicFunction('string', bar, 5, true)
myDynamicFunction('string', bar, 5, false)
Improving on my previous answer.
This aims to provide static checking that the function passed to myDynamicFunction does have one of the expected shapes (FOO or BAR).
Note the err[x] methods at the end all produce compile failures, albeit with the slightly unhelpful message "cannot be assigned to 'never'"
type FOO = (a: string, b: number) => void
type BAR = (a: number, b: string, c: boolean) => void
function isFOO(method: any, bool: boolean | undefined): method is FOO {
return typeof method === 'function' && bool === undefined
}
function isBAR(method: any, bool: boolean | undefined): method is BAR {
return typeof method === 'function' && bool !== undefined
}
const foo = (a: string, b: number) => {
console.log('foo', a, b)
}
const bar = (a: number, b: string, c: boolean) => {
console.log('bar', a, b, c)
}
const myDynamicFunction = <T extends FOO | BAR>(
str: string,
method: T & (Parameters<T> extends Parameters<FOO> ? T : Parameters<T> extends Parameters<BAR> ? T : never),
num: number,
bool?: boolean
) => {
if (isBAR(method, bool)) {
method(num, str, bool!)
} else if (isFOO(method, bool)) {
method(str, num)
} else {
throw "Fatal"
}
}
type T0 = Parameters<FOO>
myDynamicFunction('string', foo, 5)
myDynamicFunction('string', bar, 5, true)
myDynamicFunction('string', bar, 5, false)
const err1 = (a: string, b: {}): void => {}
myDynamicFunction('string', err1, 5, false)
const err2 = (a: string): void => {}
myDynamicFunction('string', err2, 5, false)
const err3 = (): void => {}
myDynamicFunction('string', err3, 5, false)
const err4 = (a: number, b: string, c: {}): void => {}
myDynamicFunction('string', err4, 5, false)
const err5 = (a: number, b: string, c: boolean, d: any): void => {}
myDynamicFunction('string', err5, 5, false)