【问题标题】:Typescript conditional types inferred by high order function高阶函数推断的打字稿条件类型
【发布时间】:2019-02-28 22:44:05
【问题描述】:

我有一个可以返回同步或异步结果的函数

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&lt;boolean&gt;,但我会失去在同步上下文中调用invokeHandlers() 的可能性,它希望避免这种情况。

有什么建议或其他设计选择来解决这个问题吗?谢谢!

【问题讨论】:

  • 我可能有一个解决方案给你,但你的代码没有T参数的结构依赖,所以我打算排除它。
  • 泛型是从真实代码中复制粘贴过来的,这只是一个简化

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


【解决方案1】:

这是我的处理方法:

为每个可能的钩子处理程序提出不同的类型:

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

然后让MyClass 依赖于你使用的HH 的类型HookHandlerinvokeHandlers 的返回类型可以是 conditional type,如果 HHSyncHookHandler,则计算结果为 boolean,如果 HHAsyncHookHandlerAsyncHookHandler | SyncHookHandler,则计算结果为 Promise&lt;boolean&gt;

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】:

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

    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,特别是关于重载的部分

    【讨论】:

    • 谢谢,我试试看
    【解决方案3】:

    其中一些可能会返回承诺是事实。这是 TypeScript 可以知道的最多的东西。

    是否所有返回的 Promise 只能在运行时确定。

    所以答案是否定的,TypeScript 无法推断出只能在运行时推断的东西。

    【讨论】:

    • 有道理。可能是您将invokeHandlers() 拆分为两个函数的解决方案,一个返回boolean,另一个返回Promise&lt;boolean&gt;
    • 是的,但是只能返回 boolean 的那个必须以这样的方式输入,它的参数永远不能是承诺,迫使你(作为程序员)使用正确的从头开始。这基本上会消除能够混合异步和同步处理程序的优势。或者你可以在运行时而不是编译时进行检查,但是你失去了 TypeScript 的优势。
    • 是的,这就是重点。目前,如果调用者使用 invokeHandlers() 的同步版本,我将忽略异步处理程序,但我正在寻找更干净的解决方案
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-22
    • 2020-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多