Home > Software design >  How can I return the ReturnType of functions from a list of parameters in Typescript?
How can I return the ReturnType of functions from a list of parameters in Typescript?

Time:07-19

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);
}

Typescript Playground

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]>>};
};

Refactored

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;
};
  • Related