0

I have a generic function fetcher which accepts a callback parameter. This callback will be executed in the fetcher function later on (I'm using Vue composables pattern here). Further, these callbacks accepts different parameters as an argument. I've typed fetcher callback that accepts "params" parameter as an union type of possible params. Then I can use this fetcher function and pass callbacks with different parameters.

Code example:

type Param1 = {
  a: string;
}
type Param2 = {
  a: string;
  b: string;
}
type Params = Param1 | Param2
declare function fetcher(func: (params: Params) => void): void
declare function testFunc(obj: Param2): void
fetcher(testFunc)

Typescript error:

Argument of type '(obj: Param2) => void' is not assignable to parameter of type '(params: Params) => void'.
  Types of parameters 'obj' and 'params' are incompatible.
    Type 'Params' is not assignable to type 'Param2'.
      Property 'b' is missing in type 'Param1' but required in type 'Param2'.(2345)

How can I type fetcher function and pass a callback with declared params, like Param1 or Param2 which fetcher can accept?

Playground example

4
  • 1
    What is the intent here? Is Params2 just always more that Params1? If so just use Prams2 in callback signature. Functions that expect less will work typescriptlang.org/play?#code/… Commented Jan 28, 2022 at 13:15
  • "Further, these callbacks accepts different parameters as an argument." and are these different parameters mutually incompatible? Your example has Param2 extends Param1 but if that's not true in general (e.g., maybe Param2 should be {a: number}), then how would the fetcher() implementation know which parameters to pass to it? This is important because while you could potentially rewrite fetcher()'s call signature like ((x: Param1) => void) | (x: Param2) => void) and even do so programmatically from Params, such a union of functions would be almost useless in practice. Commented Jan 28, 2022 at 14:58
  • And the correct solution would be that fetcher() needs something like a discriminated union input that lets the implementation know which of these callback types it's getting, so it knows how to call it. Commented Jan 28, 2022 at 15:00
  • Like, this is how I might answer your question, but it would be irresponsible, and instead I'd suggest something more like this. What do you want to see here? Commented Jan 28, 2022 at 15:12

1 Answer 1

1

TLDR: declare function fetcher(func: (params: Param1 & Param2) => void): void

Explanation:

The declaration:

declare function fetcher(func: (params: Param1 | Param2) => void): void

Instructs the compiler that func must support both Param1 and Param2 types.

Remember:

{ a: string; b: string } | { a: string; } !== { a: string; } & { b?: string }

Type union for function arguments:

  1. means that the function can handle both the situations: {a} and {a,b}
  2. does not mean that parameter {b} is optional, so {a}|{a,b} != {a,b?}

If you want to tell the compiler that fetcher accepts either func(p: Param1) or func(p: Param2) you have two possibilities:

// 1:
declare function fetcher(func: (params: Param1 & Param2) => void): void

// 2:
declare function fetcher(func: ((p: Param1) => void) | ((p: Param2) => void)): void

And the second one is more semantically correct as it tell the compiler that functions with both arguments are welcome.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.