由高阶函数推断出的Typescript条件类型

时间:2019-02-28 14:11:54

标签: typescript design-patterns promise async-await conditional-types

我有一个可以返回同步或异步结果的功能

type HookHandler<T> = (context: MyClass<T>) => boolean | Promise<boolean>;

和一个包含该功能列表的类

class MyClass<T> {

    constructor(private handlers: Array<HookHandler<T>>) {

    }

    public invokeHandlers() : boolean | Promise<boolean> {
        // invoke each handler and return:
        // - Promise<boolean> if exist a handler that return a Promise<T>
        // - boolean if all handlers are synchronous
    }

}

我想知道是否有机会使打字稿根据给定的处理程序来推断invokeHandlers()的返回类型。考虑到所有处理程序都是在设计时声明的:

const myClassSync = new MyClass<MyType>([
   (ctx) => true,
   (ctx) => false
]);

const myClassAsync = new MyClass<MyType>([
   async (ctx) => Promise.resolve(true),
   async (ctx) => Promise.reject()
]);

const myClassMix = new MyClass<MyType>([
   async (ctx) => Promise.resolve(true),
  (ctx) => true
]);

是否可以使invokeHandlers()的返回类型取决于当前给定的处理程序的类型,而无需显式强制转换?例如

// all handlers are sync, infer boolean
const allHandlersAreOk: boolean = myClassSync.invokeHandlers()

// all handlers are async, infer Promise<boolean>
const allAsyncHandlersAreOk: Promise<boolean> = await myClassAsync.invokeHandlers()

// at least one handler is async, infer Promise<boolean>
const allMixedHandlersAreOk: Promise<boolean> = await myClassMix.invokeHandlers()

我显然可以返回一个简单的Promise<boolean>,但是我会放弃在同步上下文中调用invokeHandlers()的可能性,而它想避免这种情况。

有什么建议或其他设计选择可以解决问题?谢谢!

3 个答案:

答案 0 :(得分:1)

如果您有办法区分处理程序或在运行时以某种方式标识它们,则可以使用重载

function handler(x: number): string;
function handler(y: string): number;
function handler(arg) {
    if (typeof arg === 'number') {
        return `${arg}`
    } else {
        return parseInt(arg);
    }
}

const inferred = handler(1); // <-- typescript correctly infers string
const alsoInferred = handler('1'); // <-- typescript correctly infers number

因此,如果您可以编写类似这样的内容:

function handler(context: AsyncHandler): Promise<boolean>;
function handler(context: MixedHandlers): Promise<boolean>;
function handler(context: SyncHandlers): boolean:
function handler(context){
  // your implementation, maybe instanceof if each type has a class representation
}

TypeScript可以正确推断返回类型。我不确定这是否可能基于您的代码结构,但我想我会分享。阅读更多here,特别是有关重载的部分

答案 1 :(得分:1)

这是我的处理方法:

为每种可能的钩子处理程序添加单独的类型:

type SyncHookHandler = (context: MyClass<any>) => boolean;
type AsyncHookHandler = (context: MyClass<any>) => Promise<boolean>;
type HookHandler = AsyncHookHandler | SyncHookHandler;

然后使MyClass取决于您使用的HH的类型HookHandlerinvokeHandlers的返回类型可以是conditional type,如果booleanHH,则返回SyncHookHandler;如果Promise<boolean>,则返回HHAsyncHookHandlerAsyncHookHandler | SyncHookHandler

class MyClass<HH extends HookHandler> {

  constructor(private handlers: Array<HH>) { }

  public invokeHandlers(): Promise<boolean> extends ReturnType<HH> ? 
    Promise<boolean> : boolean;
  public invokeHandlers(): boolean | Promise<boolean> {

    const rets = this.handlers.map(h => h(this));

    const firstPromise = rets.find(r => typeof r !== 'boolean');
    if (firstPromise) {
      return firstPromise; // ‍ what do you want to return here
    }
    // must be all booleans
    const allBooleanRets = rets as boolean[];
    return allBooleanRets.every(b => b);  // ‍ what do you want to return here 
  }
}

我只是在invokeHandlers()内做了一些愚蠢的实现,以了解您在那做什么。现在您可以看到您的代码按预期运行

const myClassSync = new MyClass([
  (ctx) => true,
  (ctx) => false
]);
// all handlers are sync, infer boolean
const allHandlersAreOk: boolean = myClassSync.invokeHandlers()

const myClassAsync = new MyClass([
  async (ctx) => Promise.resolve(true),
  async (ctx) => Promise.reject()
]);
// all handlers are async, infer Promise<boolean>
// note you do not "await" it, since you want a Promise
const allAsyncHandlersAreOk: Promise<boolean> = myClassAsync.invokeHandlers()

const myClassMix = new MyClass([
  async (ctx) => Promise.resolve(true),
  (ctx) => true
]);
// at least one handler is async, infer Promise<boolean>
// note you do not "await" it, since you want a Promise
const allMixedHandlersAreOk: Promise<boolean> = myClassMix.invokeHandlers()

对您有用吗?

请注意,由于示例代码对通用参数T没有结构上的依赖性,因此我已经removed it。如果需要,可以将其重新添加到适当的位置,但是我假设问题更多是关于“可以检测到同步”,而不是关于某些泛型。

好的,希望能有所帮助;祝你好运!

答案 2 :(得分:0)

其中一些人可能会兑现承诺是事实。这是 知道的最多TypeScript。

如果不是,则只能在运行时确定所有返回的诺言。

因此答案是否定的,TypeScript无法推断只能在运行时推断的内容。