如何基于输入类型在打字稿中指定承诺的返回类型

时间:2020-08-16 23:52:22

标签: typescript generics typescript-generics

我试图在TypeScript中创建一个“汇总”服务,该服务可以根据对象的类型保存对象,但是TypeScript告诉我我在做非法的事情(TS2322)。

这是我的代码的简化版本;首先,我有一个枚举,描述了我拥有的所有对象类:

enum ThingType {
    SomeThing = 'SomeThing',
    OtherThing = 'OtherThing',
}

然后有一个描述基本类型以及几个派生类型的接口:

interface IThing {
    name: string;
    type: ThingType;
}

interface ISomeThing extends IThing {
    // properties of ISomeThing
}

interface IOtherThing extends IThing {
    // properties of IOtherThing
}

对于每种派生类型,都有一种特定的服务可以保存这些类型的对象:

function saveSomeThing(someThing: ISomeThing): Promise<ISomeThing> {
    return Promise.resolve(someThing);
}

function saveOtherThing(otherThing: IOtherThing): Promise<IOtherThing> {
    return Promise.resolve(otherThing);
}

问题

现在,我希望有一个“汇总”服务,将保存操作委派给正确的实现:

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告诉我我的return语句无效:

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'.

这很公平-TypeScript不知道T的那个分支的ISomeThing将是switch-有没有办法我可以告诉TypeScript它是(没有求助于unknownany)?

1 个答案:

答案 0 :(得分:1)

有两种方法:

as any标记返回(由于函数本身是键入的,因此不会丢失任何类型信息)

在这里主要是因为另一种方式比较麻烦,并且不会增加任何实际价值:

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);
    }
}

Playground

使用类型保护功能和条件返回类型

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;
    }
}

Playground