Как я могу объявить типы в машинописном тексте функции, которая возвращает результат одного из своих обратных вызовов?

#typescript #typescript-typings #typescript-generics #typescript2.0

Вопрос:

У меня есть mapResponse функция, которая принимает другую функцию (вызывающую) и функцию сопоставления. Результатом функции должна быть 3-я функция, представляющая собой комбинацию предоставленных обратных вызовов: она должна принимать параметры вызывающего абонента, но возвращает результат сопоставления.

Вызывающий может привести к Обещанию, поэтому Обещание должно быть распаковано до того, как картограф получит свои параметры.

Поэтому мой вопрос не в реализации (что уже сделано), а в типизациях. Как я могу описать такое поведение с помощью объявлений машинописного текста?

 export function mapResponse<
  Caller extends (...args: any[]) => unknown,
  Mapper extends <R>(response: Unpacked<ReturnType<Caller>>) => R
>(
  caller: Caller,
  mapper: Mapper
): (...args: Parameters<Caller>) => ReturnType<Mapper> {
  return (...args: Parameters<Caller>) => {
    const res = caller(...args) as Unpacked<ReturnType<Caller>>
    if (res instanceof Promise) {
      return res.then(mapper) as ReturnType<Mapper>
    }
    return mapper(res)
  }
}
 

Вот Unpacked декларация, если она кому-то нужна:

 export type Unpacked<T> = T extends (infer U)[]
  ? U : T extends (...args: any[]) => infer U
  ? U : T extends Promise<infer U>
  ? U : T
 

Ответ №1:

Я бы объявил функцию следующим образом:

 type Unpacked<T> = T extends Promise<infer U> ? U : T;

declare function mapResponse
    <
        TCaller extends (...args: any[]) => any,
        TMapper extends (input: Unpacked<ReturnType<TCaller>>) => any
    >(caller: TCaller, mapper: TMapper):
        TMapper extends (input: Unpacked<ReturnType<TCaller>>) => infer R ? (...args: Parameters<TCaller>) => R : never;


const func1 = mapResponse((a: number, b: number) => a   b, sum => sum.toString()); // (a: number, b: number) => string
const func2 = mapResponse((a: number, b: number) => Promise.resolve(a   b), sum => sum.toString()); // (a: number, b: number) => string
 

Разница по сравнению с вашим примером заключается в том, что TMapper также должно быть extends определение с правой стороны, чтобы infer R правильно.

Пример игровой площадки для машинописи здесь: https://tsplay.dev/wOav6m