简体   繁体   中英

Make return type of function depends on its arguments with TypeScript?

I have a function that returns a promise which can be either of 2 types:

async profilesAll(
    ids: readonly number[],
    profileType: ProfileType.Staff | ProfileType.Pupil,
  ): Promise<(DatabaseSchema.staffProfiles | DatabaseSchema.pupilProfiles)[]> {
   // lots of logic here
}

Elsewhere I'm calling this function. otherFunction 's return type is an array of DatabaseSchema.pupilProfiles

const otherFunction = () => {
    const results = await ctx.models.room.profilesAll(
        [root.id],
        ProfileType.Pupil,
      );
    return results;
}

TypeScript throws an error as it believes the return type can be DatabaseSchema.staffProfiles or DatabaseSchema.pupilProfiles . However in reality the logic within profilesAll prevents this.

How can I remove this error? Can I make profilesAll 's return type depend on the profileType argument? I looked at conditional types but I can only see examples of them extending types, not using function arguments as I need.

You should use overloads:

async function profilesAll(ids: number[], profileType: ProfileType.Pupil): Promise<(DatabaseSchema.pupilProfiles)[]>
async function profilesAll(ids: number[], profileType: ProfileType.Staff): Promise<(DatabaseSchema.staffProfiles)[]>
async function profilesAll(
  ids: readonly number[],
  profileType: ProfileType.Staff | ProfileType.Pupil,
): Promise<DatabaseSchema.staffProfiles[]> | Promise<DatabaseSchema.pupilProfiles[]> {
   // lots of logic here
}

I don't think you can modify the return type based on the input parameter dynamically. However, in this specific case, you can specify the return type of your otherFunction by explicitly defining it.

const otherFunction = (): DatabaseSchema.pupilProfiles => { ... }

This way your otherFunction return type is no longer DatabaseSchema.staffProfiles | DatabaseSchema.pupilProfiles DatabaseSchema.staffProfiles | DatabaseSchema.pupilProfiles but DatabaseSchema.pupilProfiles only.

You can use function/method overloading for this declaration.

Method

class Foo {
  //option 1:
  async profilesAll(ids: readonly number[], profileType: ProfileType.Staff): Promise<DatabaseSchema.staffProfiles[]>; 
  //option 2:
  async profilesAll(ids: readonly number[], profileType: ProfileType.Pupil): Promise<DatabaseSchema.pupilProfiles[]>; 
  //implementation:
  async profilesAll(ids: readonly number[], profileType: ProfileType.Staff | ProfileType.Pupil): Promise<(DatabaseSchema.staffProfiles | DatabaseSchema.pupilProfiles)[]> {
      if (profileType === ProfileType.Staff) return getStaff(ids);
      
      return getPupils(ids);
  }
}

Note that you need the overloaded signatures first, then and the third implementation signature should be a merge of them. See more on overloading here .

This will then allow you to make type safe calls:

declare const instance: Foo;
//OK:
const pupils = await instance.profilesAll([1, 2, 3], ProfileType.Pupil);
//OK:
const staff  = await instance.profilesAll([1, 2, 3], ProfileType.Staff);
//Error - return type is not pupilProfiles:
const wrong: DatabaseSchema.pupilProfiles[]  = await instance.profilesAll([1, 2, 3], ProfileType.Staff);

Playground Link

Function

Using functions and overloading them, is analogous but you wouldn't have a class in that case:

//option 1:
async function profilesAll(ids: readonly number[], profileType: ProfileType.Staff): Promise<DatabaseSchema.staffProfiles[]>;
//option 2:
async function profilesAll(ids: readonly number[], profileType: ProfileType.Pupil): Promise<DatabaseSchema.pupilProfiles[]>;
//implementation:
async function profilesAll(ids: readonly number[], profileType: ProfileType.Staff | ProfileType.Pupil): Promise<(DatabaseSchema.staffProfiles | DatabaseSchema.pupilProfiles)[]> {
    if (profileType === ProfileType.Staff) return getStaff(ids);
    
    return getPupils(ids);
}

/* ... */

//OK:
const pupils = await profilesAll([1, 2, 3], ProfileType.Pupil);
//OK:
const staff  = await profilesAll([1, 2, 3], ProfileType.Staff);
//Error - return type is not pupilProfiles:
const wrong: DatabaseSchema.pupilProfiles[]  = await profilesAll([1, 2, 3], ProfileType.Staff);

Playground Link

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