简体   繁体   中英

Union type of two functions in Typescript

I have a situation where I have a type which is a union of two functions which have a literal value as the type of one of their arguments. Boiled down to a minimal example, it is essentially this:

type FetchMini = (id: number, representation: 'mini') => MiniUser;
type FetchFull = (id: number, representation: 'full') => FullUser;

function f(fetchUser: FetchMini | FetchFull) {
  fetchUser(2, 'mini');
  fetchUser(2, 'full');
}

Both of these calls fail the compiler with the message:

Cannot invoke an expression whose type lacks a call signature. Type 'FetchMini | FetchFull' has no compatible call signatures.`

I think I understand the reason why this happens, (...I think) but what could I do about it?

Here's a similar question, however it got no answers: Union of functions is not callable even with parameters that meet each function's constraints

Your fetchUser() is essentially an overloaded function which accepts both mini and full , thus should be intersection -typed, rather than union.

type MiniUser = { name: string }
type FullUser = { firstName: string, lastName: string, age: number }

type FetchMini = (id: number, representation: 'mini') => MiniUser;
type FetchFull = (id: number, representation: 'full') => FullUser;
type FetchBoth = FetchMini&FetchFull

function f(fetchUser: FetchBoth) {
    fetchUser(2, 'mini');
    fetchUser(2, 'full');
}

function fetchBoth(id: number, representation: 'mini'): MiniUser
function fetchBoth(id: number, representation: 'full'): FullUser
function fetchBoth(id: number, representation: 'mini' | 'full') {
    if (representation==='mini')
        return { name: 'Mini' }
    if (representation==='full')
        return { firstName: 'Full', lastName: 'User', age: 20 }
}
f(fetchBoth)

PlayGround

As a rule of thumb, when you declare a function that accepts args of both type A and B, the function should be & -typed.

Well, it looks like the initial idea is a bit incorrect. You should decide on what behavior you'd like to achieve.

From what I can see, f accepts a function which is responsible for fetching a user. Depending on the provided function either MiniUser or FullUser will be returned. If my suggestion is right, than consider the following example:

class MiniUser {
    name: string;

    constructor(name: string) {
        this.name = name;
    }
}

class FullUser extends MiniUser {
    age: number;

    constructor(name: string, age: number) {
        super(name);
        this.age = age;
    }
}

function fetchMiniFn(id: number) {
    return new MiniUser("John");
}

function fetchFullFn(id: number) {
    return new FullUser("John", 22);
}

function f(fetchUser: (id: number) => MiniUser | FullUser) {
    let a = fetchUser(2);
    if (a instanceof FullUser) {
        console.log("Full User: " + a.name + ", " + a.age);
    }
    else {
        console.log("Mini User: " + a.name);
    }
}

// Call the function to fetch MiniUser
f(fetchMiniFn);

// Call the function to fetch FullUser
f(fetchFullFn);


If my initial suggestion is incorrect and you still want the function f to decide what kind of User must be fetched than you can transform the code above to:

function fetch(id: number, representation: 'mini' | 'full') {
    if (representation == 'mini') {
        return new MiniUser("John");
    }
    else {
        return new FullUser("John", 22);
    }
}

type FetchUser = (id: number, representation: 'mini' | 'full') => MiniUser | FullUser;

function f(fetchUser: FetchUser) {
    // Call the function with MiniUser fetching
    let mini = <MiniUser>fetchUser(2, 'mini');
    console.log("Mini User: " + mini.name);

    // Call the function with FullUser fetching
    let full = <FullUser>fetchUser(2, 'full');
    console.log("Full User: " + full.name + ", " + full.age);
}

// Call function:
f(fetch);

you can solve it easly with generic and extends ( work for me in typescript 2.7.2 )

type FetchMini = (id: number, representation: 'mini') => MiniUser;
type FetchFull = (id: number, representation: 'full') => FullUser;

function f <T extends FetchMini | FetchFull >(fetchUser: T) {

    fetchUser(2, 'mini');
    fetchUser(2, 'full');
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM