I have a main function myMainFunction()
that takes in a list of functions as parameters. Each of these parameter functions can have a different return type.
I would like myMainFunction
have its return type as the return types of the functions passed into it as parameters. However, I can't quite figure out to do it.
Here's what I have which doesn't seem to work:
const funcA = async (): string => "Hello";
const funcB = async (): {name: string, age: number} => ({ name: "Jane", age: 123 });
const myMainFunction = async <F extends () => ReturnType<F>>(...functions: F[]): Promise<ReturnType<F>[]> => {
const res = await Promise.all(functions.map((fn) => fn()));
return res;
};
// This doesn't work as values has a type of Promise<string>[]
// I'm expecting values to have a type of Promise<[string, {first: string, age: number }]>
const values = await myMainFunction(funcA, funcB);
What I have above doesn't work as the the variable values
has a type of Promise<string>[]
. For some reason, it only sees the return type of the first function in the parameters.
I'm expecting that values
will have type of Promise<[string, {first: string, age: number }]>
which are essentially the return types of funcA()
and funcB()
.
How can I achieve it?
CodePudding user response:
Typescript has a special type for Array types T[number]
, so using this we can do->
const funcA = async (): Promise<string> => "Hello";
const funcB = async (): Promise<{name: string, age: number}> => ({ name: "Jane", age: 123 });
const myMainFunction = async <T extends any[number][]> (...functions: T) => {
const res = await Promise.all(functions);
return res;
};
async function test() {
const values = await myMainFunction(funcA(), funcB());
// values[0] string
// values[1] {name: string, age: number}
console.log(values[1].age);
}
The problem when you pass the functions, the map function loses the Type. But we could just put them back in using as
. Thanks to @T.J.Crowder I used his MapToUnpromisedReturnType helper type and got this ->
const funcA = async (): Promise<string> => "Hello";
const funcB = async (): Promise<{name: string, age: number}> => ({ name: "Jane", age: 123 });
type MapToUnpromisedReturnType<T extends Array<any>> = {
[K in keyof T]: Awaited<ReturnType<T[K]>>;
};
const myMainFunction = async <T extends (() => Promise<any[number]>)[]> (...functions: T) => {
const mapped = await functions.map(f => f());
return mapped as MapToUnpromisedReturnType<T>;
};
async function test() {
const values = await myMainFunction(funcA, funcB);
// values[0] string
// values[1] {name: string, age: number}
console.log(values[1].age);
}
Playground 2. passing promise functions
ps: there may be a cleaner way, but it does seem to work nicely..
Just a slightly refactored version to take away helper ->
const myMainFunction = async <T extends (() => Promise<any[number]>)[]> (...functions: T) => {
return functions.map(f => f()) as {[K in keyof T]: Awaited<ReturnType<T[K]>>};
};
CodePudding user response:
Old and not so clean version, don't use.
type ResolveFunction<T extends (...args: any) => any> = Awaited<ReturnType<T>>;
type ResolveFunctions<T extends [...((...args: any) => any)[]]> = T extends [
infer Head,
...infer Tail
]
? Head extends (...args: any) => any
? Tail extends ((...args: any) => any)[]
? [ResolveFunction<Head>, ...ResolveFunctions<Tail>]
: []
: []
: [];
const myMainFunction = async <T extends [...((...args: any) => any)[]]>(
...functions: T
): Promise<ResolveFunctions<T>> => {
const res = await Promise.all(functions.map((fn) => fn()));
return res as any;
};
New version after looking at another answer.
type AsyncFunction = (...args: any) => Promise<any>;
type ResolveFunction<T extends AsyncFunction> = Awaited<ReturnType<T>>;
type ResolveFunctions<T extends AsyncFunction[]> = {
[K in keyof T]: ResolveFunction<T[K]>;
};
const myMainFunction = async <T extends AsyncFunction[]>(
...functions: T
): Promise<ResolveFunctions<T>> => {
const res = await Promise.all(functions.map((fn) => fn()));
return res as any;
};