silk
silk

Reputation: 189

how to implement typescript function overload

How to implement typescript function overload ?

interface Fn <T>{
  (payload: string): T;
  (payload: string, a: T): T;
}

let fn: Fn<number> =  function(a: string, b: number) {
  return b
}

report Type '(a: string, b: number) => string' is not assignable to type 'Fn<number>'.Vetur(2322)

Upvotes: 1

Views: 137

Answers (2)

jcalz
jcalz

Reputation: 327634

In order to satisfy an interface with multiple (i.e., overloaded) call signatures, a function's implementation must be able to handle all such signatures. If fn is a Fn<number>, then you must be able to call it like this:

fn("someString"); // first overload
fn("someString", 1234); // second overload

But the implementation

function (a: string, b: number) {
  return b 
}

does not satisfy the first overload (since b would be undefined), and so is not assignable to Fn<number>.

Instead, you should consider coming up with an implementation compatible with both call signatures. For example:

let fn: Fn<number> = function (a: string, b?: number) {
  return b ?? a.length
}

Here, the implementation requires its first parameter a to be a string and takes an optional second parameter b of type number. Since b might be undefined and you have to return a number, you can't just return b directly. In the above I return b unless it is undefined, in which case I return a.length. This is always a number and the compiler can verify the assignment as valid.


Note: there is a complication in situations where the overloaded call signatures have different return types. Unless your function expression's implementation returns the intersection of all the return types, the compiler will not be able to verify type safety and will error, so you'll need to use a type assertion:

interface OtherFn {
  (payload: string): string;
  (payload: string, a: number): number;
}

let fn2: OtherFn = (a: string, b?: number) => b ?? a; // error!
//  ~~~ <-- string | number not assignable to string

console.log(fn2("abc").toUpperCase()) // ABC
console.log(fn2("abc", Math.PI).toFixed(2)) // 3.14

Here you can see that fn2 does satisfy both call signatures in OtherFn, but the compiler is unable to verify it because of the different return types. You'll need a type assertion if you want to do this with function expressions:

let fn3 = ((a: string, b?: number) => b ?? a) as OtherFn; // okay

or you can abandon function expressions and use function statements, which are checked more loosely, allowing the return type to be the union of all the call signatures return types:

function fn4(payload: string): string;
function fn4(payload: string, a: number): number;
function fn4(a: string, b?: number) {
  return b ?? a;
}

which can be seen to satisfy OtherFn despite not being manually annotated as such.

let fn5: OtherFn = fn4; // okay

Playground link to code

Upvotes: 1

Linda Paiste
Linda Paiste

Reputation: 42160

In order for both of those overloads to apply to fn, you have to make b optional in your implementation. When you do that, you'll see a different error because our type says that we must return a number, so returning b when b might be undefined doesn't cut it. We can either set a default value for b, or return some default value within the body of the function.

let fn: Fn<number> =  function(a: string, b: number = 0) {
  return b;
}
let fn: Fn<number> =  function(a: string, b?: number) {
  return b === undefined ? 0 : b;
}

Upvotes: 1

Related Questions