Reputation: 23593
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.
Upvotes: 0
Views: 178
Reputation: 3639
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
}
Upvotes: 1
Reputation: 29007
You can use function/method overloading for this declaration.
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);
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);
Upvotes: 0
Reputation: 6557
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
but DatabaseSchema.pupilProfiles
only.
Upvotes: 0