网站搜索

typescript - 如何获取以下变量的对应类型


interface Model {
  bar: number
  foo: string
}

async function getSth(a: number, b: string): Promise<Model> {
  return await Promise.resolve({
    bar: a,
    foo: b,
  })
}

function createA(m: Model): number {
  return m.bar
}

function createB(m: Model): string {
  return m.foo
}

GetSth 是一个异步函数。createA 和 createB 都需要依赖它的返回值作为参数来运行。所以我想创建一个带有记忆功能的异步函数,以便于使用,如下所示:

function memorifyFuncs<T extends any[]>(asyncFunc: (...args: T) => Promise<any>) {
  return <Fs extends ((...args: any[]) => any)[]>(...createFuncs: Fs) => {
    const cache: Record<string, any> = {}

    return createFuncs.map(
      <F extends (...args: any[]) => any>(func: F) =>
        async (...args: T): Promise<ReturnType<F>> => {
          const key = args.map((arg) => JSON.stringify(arg)).join('&')
          if (!cache[key]) {
            cache[key] = await asyncFunc(...args)
          }
          return func(cache[key])
        }
    )
  }
}

MemoryFuncs 接收类似于 getSth 的异步函数,在内部设置缓存,然后接收并构建类似于 createA 和 createB 的函数。它缓存实现过程,并通过 map 进行映射。然而,我发现返回的对应函数无法获取它的返回值类型:

在此输入图片描述

获取下面变量的对应类型

答案#1

你需要使 memorityFuncs 的调用签名更加通用,可能如下所示:

declare function memorifyFuncs<T extends any[], R>(
  asyncFunc: (...args: T) => Promise<R>
): <O extends any[]>(...createFuncs: {
  [I in keyof O]: (arg: R) => O[I]; }
) => { [I in keyof O]: (...args: T) => Promise<O[I]>; }

在这里,我们希望 memorifyFuncs 不仅在对应于 asyncFunc 回调的参数列表的 T 上是通用的,而且在对应于该回调的 await 返回类型的 R 上也是通用的。你还需要返回的函数在 O 上是通用的,O 是 createFuncs 中每个函数的 await 输出类型的元组。因此,如果 createFuncs 的类型是 [(x: string) => number, (x: string) => boolean, (x: string) => Date],那么 O 将是 [number, boolean, Date]。

然后我们可以将 createFuncs 的类型和返回类型写成映射元组类型。createFuncs 的类型是 {[I in keyof O]: (arg: R) => O[I]},这意味着它是一个函数数组:每个函数都以 R 作为输入,并输出 O 的对应元素类型。输出的类型是 {[I in keyof O]: (...args: T) => Promise<O[I]>},这意味着它也是一个函数数组:每个函数都接受与 asyncFunc 相同的输入,并输出对应 O 元素类型的 Promise。注意,因为 createFuncs 的类型是针对 O 的 同态映射类型(参见“同态映射类型是什么意思?”),编译器能够从 createFuncs 推断 出 O(否则表达起来会更复杂)。


我不会去担心让编译器对 memorifyFuncs 的实现感到满意。它不可能判断你的算法是否遵守了映射元组类型的规则。你应该直接用断言解决这个问题,或者使用 any 类型来放宽检查:

function memorifyFuncs<T extends any[], R>(asyncFunc: (...args: T) => Promise<R>) {
  return <O extends any[]>(...createFuncs: { [I in keyof O]: (arg: R) => O[I] }):
    { [I in keyof O]: (...args: T) => Promise<O[I]> } => {
    const cache: Record<string, any> = {}
    return createFuncs.map(
      (func: any) =>
        async (...args: any) => {
          const key = args.map((arg: any) => JSON.stringify(arg)).join('&')
          if (!cache[key]) {
            cache[key] = await asyncFunc(...args)
          }
          return func(cache[key])
        }
    ) as any
  }
}

好吧,我们来试试:

const [funcA, funcB] = memorifyFuncs(getSth)(createA, createB)
// const funcA: (a: number, b: string) => Promise<number>
// const funcB: (a: number, b: string) => Promise<string>

async function foo() {
  const n = await funcA(1, "two");
  console.log(n.toFixed(2)) // "1.00"
  const s = await funcB(2, "three");
  console.log(s.toUpperCase()) // "THREE"
}
foo();

看起来不错。编译器推断 funcA 的类型是 (a: number, b: string) => Promise<number>,因为 createA 返回一个 number……并且它推断 funcB 的类型是 (a: number, b: string) => Promise<string>,因为 createB 返回一个 string。

代码的 Playground 链接