高阶函数推断的打字稿条件类型

Typescript conditional types inferred by high order function

我有一个函数可以return同步或异步结果

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

和一个 class 获取该函数的列表

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() 的 return 类型。考虑到所有处理程序都是在设计时声明的:

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() 的 return 类型依赖于当前给定处理程序的类型而无需显式转换吗?例如

// 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()

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

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

他们中的一些人可能 return 承诺是事实。这是 TypeScript 可以 知道的最多的。

是否全部 returning promises 只能在 运行 时间确定。

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

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

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 可以正确推断出 return 类型。根据您的代码结构,我不确定这是否可行,但我想我会分享。阅读更多内容 here,特别是有关重载的部分

以下是我的处理方法:

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

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

然后根据你使用的HookHandler类型HH使MyClassinvokeHandlers 的 return 类型可以是 conditional type 如果 HHSyncHookHandler,则计算结果为 boolean,如果 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。如果您需要它,您可以将它添加回适当的位置,但我假设问题更多是关于检测同步,如果你可以,而不是一些通用类型。

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