Home > Enterprise >  Typescript: Error on function argument when the function can be of multiple function types
Typescript: Error on function argument when the function can be of multiple function types

Time:11-14

I've got the following example code:

type GetMultipleFunction1 = () => Promise<string[]>; 
type GetMultipleFunction2 = (data: number) => Promise<string[]>; 
type GetMultipleFunction = GetMultipleFunction1 | GetMultipleFunction2;

function test(func: GetMultipleFunction): Promise<string[]> {
    return func(); 
}

function getStrings(data: number): Promise<string[]> {
    return new Promise(function (resolve, reject) {
        resolve(['hello', 'hi']);
    }) }

test(getStrings);

Why do I get the error (line 6 on return func()):

Expected 1 arguments, but got 0.

I feel like GetMultipleFunction can be of either type 1 or type 2. Type 1 needs no arguments.

How do I need to change my code to get rid of the warning?

Typescript Playground

--

Edit: What I want to achieve:

I basically want a function that accepts a function as an argument. In my example test. And it should accept a function with an argument and a function without an argument. Is that even possible?

Code example:

type GetMultipleFunction1 = () => string[];
type GetMultipleFunction2 = (data: number) => string[];
export type GetMultipleFunction = GetMultipleFunction1 | GetMultipleFunction2;

function test(func: GetMultipleFunction, data?: number): string[] {
    if (data) return func(data);
    return func()  // here should be no warning
}

function getStrings1(data: number): string[] {
    return [`hello-${data}`, 'hi'];
}

function getStrings2(): string[] {
    return ['hello', 'hi']
}

test(getStrings1, 2);
test(getStrings2);

Typescript Playground

--

More specific explanation of what I want to achieve: I'm using Vue3 composition functions in order to extract Get requests. Usually I can just call: /api/groups/, but sometimes I need to pass an argument to my get request function: /api/groups/3/members/ Now number 3 would be needed to be passed as an argument.

CodePudding user response:

Problem is in this line type GetMultipleFunction = GetMultipleFunction1 | GetMultipleFunction2;.

Union of two functions never behave in a way you expect. It produces a function where argument is intersected type of both function arguments. See small example:

type Foo = (arg: { age: number }) => void
type Bar = (arg: { name: string }) => void

type Union = Foo | Bar

declare let func:Union

// let func: (arg: { age: number;} & { name: string;}) => void
func()

This is by design an this is the safest way to call a union of functions.

In your example:

function test(func: GetMultipleFunction): Promise<string[]> {
    return func(); // < ------ ERROR
}

The safest way of calling func is providing an argument. It will cover two cases. If you passed a function where argument is not required - it will not make any harm.

I think in this example it worh using function intersection instead union. Like here:

type GetMultipleFunction1 = () => Promise<string[]>;
type GetMultipleFunction2 = (data: number) => Promise<string[]>;
export type GetMultipleFunction = GetMultipleFunction1 & GetMultipleFunction2;

function test(func: GetMultipleFunction): Promise<string[]> {
    return func(); // no error
}

function getStrings(data: number): Promise<string[]> {
    return new Promise(function (resolve, reject) {
        resolve(['hello', 'hi']);
    })
}

test(getStrings); // error

However, we are getting error in a new place: test(getStrings); // error.

This is because Type '(data: number) => Promise<string[]>' is not assignable to type '() => Promise<string[]>'.

I is hard to make a guess what you are trying to achieve here. data argument in getStrings is unused.

You can also overload your test function:

type GetMultipleFunction1 = () => Promise<string[]>;
type GetMultipleFunction2 = (data: number) => Promise<string[]>;

function test(func: GetMultipleFunction2): Promise<string[]>
function test(func: GetMultipleFunction1): Promise<string[]>
function test(func: (...args: any[]) => Promise<string[]>): Promise<string[]> {
    return func(); // ok
}

function getStrings(data: number): Promise<string[]> {
    return new Promise(function (resolve, reject) {
        resolve(['hello', 'hi']);
    })
}

test(getStrings); // ok

test(() => Promise.resolve(42)); // expected error

Don't worry that I have used (...args: any[]), test function still accept only either GetMultipleFunction1 or GetMultipleFunction2

UPDATE

You can get rid of union function and overloadings at all. Just use rest parameters


function test<Fn extends (...args: any[]) => string[]>(func: Fn, ...params: Parameters<Fn>): string[] {
    return func(...params)  // ok
}

function getStrings1<Data extends number>(data: Data) {
    return [`hello-${data}`, 'hi'];
}

function getStrings2() {
    return ['hello', 'hi']
}

test(getStrings1, 2);
test(getStrings1); // expected error
test(getStrings2);

Playground

  • Related