1

I am looking for a way to define an object with two functions where one function always gets the return type of the other function as a parameter. E.g.

type DoActionArgType = { contactId: string };

type Args<T> = 
{
  doAction: (arg: DoActionArgType) => string; // <-- returns string
  onEnd?: (arg: string) => void; // <-- takes string as the argument
}

Now I want this to work for arbitrary return types and promises, which works fine, except if I do not add the type to the function doAction function argument.

export type $Unwrap<T> = T extends Promise<infer U>
    ? U
    : T extends (...args: any) => Promise<infer U>
    ? U
    : T extends (...args: any) => infer U
    ? U
    : T;

type DoActionArgType = { contactId: string };

type Args<T> = 
{
  doAction: (arg: DoActionArgType) => Promise<T>;
  onEnd?: (arg: $Unwrap<T>) => void;
}

function test<T>(arg: Args<T>) {}

const doAction = async ({ contactId }) => 1

test({
  // doAction: async ({ contactId }: DoActionArgType) => 1, // Works
  // doAction: async () => 1, // Works
  // doAction, // Works
  doAction: async ({ contactId }) => 1, // <--- Does not work
  onEnd: (arg) => {
    const a:number = arg
  }
})

I assume that typescript does not understand that the function signature matches ? Because contactId is actually typed string just arg of onEnd is typed as unknown. Is there any way to make this work without adding the DoActionArgType type to the doAction function ?

Typescript Playground

Philiiiiiipp
  • 705
  • 1
  • 9
  • 24

1 Answers1

2

You need to do a hat trick:

type DoActionArgType = { contactId: string };

type PromiseUnwrap<P> =
  P extends Promise<infer Value>
  ? Value
  : P

const test = <T,>(arg: {
  doAction: (a_arg: DoActionArgType) => T;
  onEnd: <U extends T>(b_arg: PromiseUnwrap<U>) => void;
}) => {
  // ...
};

test({
  doAction: async ({ contactId }) => 1,
  onEnd: (arg) => {
    arg // number
    arg.toExponential // ok
  }
})

Playground

PromiseUnwrap - obtains Promise return type.

You might ask, why this function does not work:

const test = <T,>(
  arg: {
    doAction: (a_arg: number) => T;
    onEnd: (b_arg: T) => void
  }) => {
  // ...
};

Here you can find a good answer.

If you want to learn more about callbacks in typescript, you can read my article. First example will be yours