Reputation: 6627
I'm trying to create a "rollup" service in TypeScript that can save an object based on it's type, but TypeScript is telling me I'm doing something illegal (TS2322
).
Here's a simplified version of my code; first I have an enum which describes all the object classes that I have:
enum ThingType {
SomeThing = 'SomeThing',
OtherThing = 'OtherThing',
}
Then there's an interface that describes the base type, as well as a couple of derived types:
interface IThing {
name: string;
type: ThingType;
}
interface ISomeThing extends IThing {
// properties of ISomeThing
}
interface IOtherThing extends IThing {
// properties of IOtherThing
}
For each derived type, there's a specific service that saves objects of those types:
function saveSomeThing(someThing: ISomeThing): Promise<ISomeThing> {
return Promise.resolve(someThing);
}
function saveOtherThing(otherThing: IOtherThing): Promise<IOtherThing> {
return Promise.resolve(otherThing);
}
Now I want to have a single "rollup" service that delegates the save operation to the correct implementation:
function saveThing<T extends IThing>(thing: T): Promise<T> {
switch (thing.type) {
case ThingType.SomeThing: return saveSomeThing(thing as ISomeThing);
case ThingType.OtherThing: return saveOtherThing(thing as IOtherThing);
default: throw new Error('Unknown type: ' + thing.type);
}
}
TypeScript tells me that my return
statements are invalid:
TS2322: Type 'Promise<ISomeThing>' is not assignable to type 'Promise<T>'.
Type 'ISomeThing' is not assignable to type 'T'.
'ISomeThing' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'IThing'.
That's fair enough - TypeScript doesn't know that T
will be an ISomeThing
for that branch of the switch
- is there a way I can tell TypeScript that it is (without resorting to an unknown
or any
)?
Upvotes: 1
Views: 4404
Reputation: 2812
There are two ways:
as any
(you don't lose any type information since the function itself is typed)Mostly here since the other way is rather cumbersome and does not add any real value:
function saveThing<T extends IThing>(thing: T): Promise<T> {
switch (thing.type) {
case ThingType.SomeThing: return saveSomeThing(thing as ISomeThing) as any;
case ThingType.OtherThing: return saveOtherThing(thing as IOtherThing) as any;
default: throw new Error('Unknown type: ' + thing.type);
}
}
function saveThing<T extends IThing>(thing: T): Promise<T extends ISomeThing ? ISomeThing : IOtherThing> {
type returnType = T extends ISomeThing ? ISomeThing : IOtherThing;
if(isISomeThing(thing)){
return saveSomeThing(thing) as Promise<returnType>;
}
if(isIOtherThing(thing)){
return saveOtherThing(thing) as Promise<returnType>;
}
throw new Error('Unknown type: ' + thing.type);
function isISomeThing(potentialISomeThing: IThing): potentialISomeThing is ISomeThing {
return potentialISomeThing.type === ThingType.SomeThing;
}
function isIOtherThing(potentialIOtherThing: IThing): potentialIOtherThing is IOtherThing {
return potentialIOtherThing.type === ThingType.OtherThing;
}
}
Upvotes: 3