Home > Enterprise >  Typescript deduce return type of member function of generic type
Typescript deduce return type of member function of generic type

Time:11-14

I have something I'm trying to do but I'm not quite sure how to achieve it in typescript.

I'm working with discord.js and I'm trying to make a wrapper to convert Discord.User | Discord.PartialUser (or similar T | PartialT types) to their non-partial versions.

Something like this:

interface Partial {
    partial: boolean;
    fetch(): Promise<any>;
}
async function departialize<T extends Partial>(thing: T) {
    if(thing.partial) {
        return await thing.fetch();
    } else {
        return thing;
    }
}

While this compiles, I would like to include more type information. I.e. with this example I would like it to return Promise<Discord.User> instead of Promise<Any>.

Both Discord.User.fetch() and Discord.PartialUser.fetch() return Promise<Discord.User>, can this be deduced in the interface or return type? In C we would write something like decltype(std::declval<T>().fetch()), not sure what to do in TS.

CodePudding user response:

Here's an overloaded function which will provide a correct return type for your input, narrowing based on the inferred value of the partial property.

Notes:

  • Because the Partial utility is already declared in TypeScript, I've used the type name Incomplete to avoid a compiler error.

  • unknown is more type safe than any, but feel free to replace if you really need any.

TS Playground link

interface Incomplete<T = unknown> {
  partial: boolean;
  fetch (): Promise<T>;
}

function departialize <T extends Incomplete & { partial: true }>(thing: T): ReturnType<T['fetch']>;
function departialize <T extends Incomplete & { partial: false }>(thing: T): T;
function departialize <T extends Incomplete>(thing: T): T | ReturnType<T['fetch']>;
function departialize <T extends Incomplete>(thing: T): T | ReturnType<T['fetch']> {
  return thing.partial ? thing.fetch() as ReturnType<T['fetch']> : thing;
}

// Usage:

declare const input1: {
  partial: boolean;
  fetch (): Promise<string>;
  someOtherProp: number;
};

const output1 = departialize(input1); // typeof input1 | Promise<string>

declare const input2: {
  partial: true;
  fetch (): Promise<string>;
  someOtherProp: number;
};

const output2 = departialize(input2); // Promise<string>

declare const input3: {
  partial: false;
  fetch (): Promise<string>;
  someOtherProp: number;
};

const output3 = departialize(input3); // typeof input3

CodePudding user response:

Figured out, here's what I'm going with in case it helps anyone else:

type PotentiallyPartial = Discord.AllowedPartial | Discord.Partialize<Discord.AllowedPartial>;

async function departialize<T extends PotentiallyPartial, R extends ReturnType<T["fetch"]>>(thing: T): Promise<R> {
    if(thing.partial) {
        return thing.fetch();
    } else {
        return thing as R;
    }
};
  • Related