Imagine a TypeScript (4.2.2) function that receives a string or a Promise<string> and returns an answer using the same type it has originally received, for example:
function trim(textOrPromise) {
if (textOrPromise.then) {
return textOrPromise.then(value => result.trim());
}
return textOrPromise.trim();
}
I'd like to define the signature of such function using generics to indicate that passing in a Promise always results in a Promise, and passing in a string results in a string
To do that, I define the following overloads:
function trim(text: Promise<string>): Promise<string>
function trim(text: string): string
function trim(text: any): any {
if (text.then) {
return text.then(result => result.trim()); // returns Promise<string>
}
return text.trim(); // returns string
}
This transpiles correctly as long as the parameter is defined explicitly as string or a Promise<string>:
let value: string
trim(value) // fine
let value: Promise<string>
trim(value) // fine
However, when I define the parameter using a union type (Promise<string> | string):
let value: Promise<string> | string
trim(value) // error: TS2769
I get the following transpilation error:
TS2769: No overload matches this call.
Overload 1 of 2, '(text: Promise<string>): Promise<string>', gave the following error.
Argument of type 'string | Promise<string>' is not assignable to parameter of type 'Promise<string>'.
Type 'string' is not assignable to type 'Promise<string>'.
Overload 2 of 2, '(text: string): string', gave the following error.
Argument of type 'string | Promise<string>' is not assignable to parameter of type 'string'.
Type 'Promise<string>' is not assignable to type 'string'.
Interestingly enough, when I add the union type to the function signature, the example transpiles and behaves correctly
function trim(text: Promise<string>): Promise<string>
function trim(text: string): string
function trim(text: Promise<string> | string): Promise<string> | string
function trim(text: any): any {
if (text.then) {
return text.then(result => result.trim());
}
return text.trim();
}
let value: Promise<string> | string
trim(value) // fine
With this last implementation, TypeScript knows that when a function receives a Promise it will return a Promise, and when it receives a string it returns a string. As opposed to a union of Promise<string> | string as the third overload would suggest.
I'd be grateful if someone could explain this behaviour and the reason behind having to add an overload for union types.