【问题标题】:TypeScript Typed Object Output With Same Keys as InputTypeScript 类型化对象输出与输入具有相同的键
【发布时间】:2021-06-25 00:00:32
【问题描述】:

我正在尝试编写一个函数来解析对象的所有 Promise 值:

const resolveObject = async (obj) => // pure JavaScript implementation
  Object.fromEntries( // turn the array of tuples with key-value pairs back into an object
    (await Promise.all(Object.values(obj))).map((el, i) => [ // Promise.all() the values, then create key-value pair tuple
      Object.keys(obj)[i], // tuple contains the original object key
      el, // tuple contains the resolved object value
    ]) // zips the split key/value arrays back into one object
  );

实施工作正常。下面是一个使用示例:

/* example input */
const exampleInput = { // <- { foo: Promise<string>, bar: Promise<{ test: number }>, baz: boolean }
    foo: Promise.resolve("Hello World"),
    bar: Promise.resolve({
        test: 1234
    }),
    baz: false
}
const expectedOutput = resolveObject(exampleInput) // <- { foo: string, bar: { test: number }, baz: boolean }
/* expected output: strongly typed object with same keys and no Promise<> wrappers in signature */

这就是事情开始崩溃的地方。我期待一个具有与输入相似签名的强类型输出(只是没有 Promise 包装器),但相反,我得到以下通用对象输出:

Promise<{ [k: string]: unknown; }>

于是我开始给resolveObject函数添加类型注解:

const resolveObject = async <T>(
  obj: { [K in keyof T]: Promise<T[K]> }
): Promise<{ [K in keyof T]: T[K] }> => { ... }

现在我收到一个类型转换错误:type '{ [k: string]: unknown; }' is not assignable to type '{ [K in keyof T]: T[K]; }'
我对 TypeScript 还很陌生,真的不知道下一步该做什么(我知道如何诊断错误消息,但我很确定我的类型注释/函数签名有问题)。我怎样才能实现我正在寻找的东西?

【问题讨论】:

    标签: javascript typescript object types


    【解决方案1】:

    如果您希望resolveObject() 的输出类型依赖于其输入的类型,您需要给它一个generic 函数签名; TypeScript 中没有工具可以推断函数具有这样的通用签名。

    预期的签名是这样的:

    /* const resolveObject: <T extends object>(
      obj: { [K in keyof T]: T[K] | Promise<T[K]>; }
    ) => Promise<T> */
    

    这意味着对于某些泛型类型T 的输出类型为Promise&lt;T&gt;,由输入obj 确定。特别是,obj 属于 mapped type,其中 T 在键 K 处的每个属性,即 T[K],要么单独存在 (T[K]),要么 (union |)包裹在Promise (Promise&lt;T[K]&gt;) 中。事实证明,编译器能够通过inference from mapped typesobj的类型中找出T


    不幸的是,编译器能够遵循您获得的特定实现以验证返回值是否符合类型签名的希望并不大。 Object.fromEntries()Object.keys() 的分型不够具体,无法推断出您想要什么,即使可以,Object.keys() 的索引 iObject.values() 之间的相关性也不容易表示。与其试图弄清楚如何让编译器理解实现是类型安全的,不如非常小心地确保我们做得对,然后assert 我们已经这样做了:

    const resolveObject = async <T extends object>(
      obj: { [K in keyof T]: Promise<T[K]> | T[K] }
    ) =>
      Object.fromEntries(
        (await Promise.all(Object.values(obj))).map((el, i) => [
          Object.keys(obj)[i],
          el,
        ])
      ) as T; // assertion here
    

    我们已经断言Object.fromEntries(...) 会产生T 类型的值。如果我们犯了错误,那么编译器不会注意到;类型断言将维护类型安全的负担从编译器转移到了开发人员身上。但是在这种情况下,我们在resolveObject()的实现中有一个单一的类型断言,调用者无需担心:

    const exampleInput = {
      foo: Promise.resolve("Hello World"),
      bar: Promise.resolve({
        test: 1234
      }),
      baz: false
    }
    const expectedOutput = resolveObject(exampleInput);
    /* const expectedOutput: Promise<{
        foo: string;
        bar: {
            test: number;
        };
        baz: boolean;
    }> */
    

    您可以看到 expectedOutput 的类型已被编译器推断为您所期望的:具有已知键和属性类型的对象类型的 Promise&lt;&gt;

    expectedOutput.then(x => console.log(x.bar.test.toFixed(2))) // 1234.00
    

    看起来不错。

    Playground link to code

    【讨论】:

      猜你喜欢
      • 2016-06-23
      • 1970-01-01
      • 1970-01-01
      • 2018-03-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-24
      • 2019-03-17
      相关资源
      最近更新 更多