简体   繁体   English

TypeScript 键入 Object Output 与输入相同的键

[英]TypeScript Typed Object Output With Same Keys as Input

I'm trying to write a function which resolves all Promise values of an object:我正在尝试编写一个 function 来解决 object 的所有 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
  );

The implementation works fine.实施工作正常。 Below is a usage example:下面是一个使用示例:

/* 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 */

This is where things start to fall apart.这就是事情开始分崩离析的地方。 I'm expecting a strongly typed output with a similar signature as the input (just without the Promise wrappers), but instead, I get the following generic object output: I'm expecting a strongly typed output with a similar signature as the input (just without the Promise wrappers), but instead, I get the following generic object output:

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

So I started adding type annotations to the resolveObject function:所以我开始给resolveObject function添加类型注解:

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

Now I receive a type conversion error: type '{ [k: string]: unknown; }' is not assignable to type '{ [K in keyof T]: T[K]; }'现在我收到一个类型转换错误: type '{ [k: string]: unknown; }' is not assignable to type '{ [K in keyof T]: T[K]; }' type '{ [k: string]: unknown; }' is not assignable to type '{ [K in keyof T]: T[K]; }'
I'm fairly new to TypeScript and don't really know what to do next (I know how to diagnose the error message, but I'm pretty sure that something with my type annotation/function signature is wrong).我对 TypeScript 还很陌生,我真的不知道下一步该做什么(我知道如何诊断错误消息,但我很确定我的类型注释/函数签名有问题)。 How can I achieve what I'm looking for?我怎样才能实现我正在寻找的东西?

If you would like the type of resolveObject() 's output to be dependent on the type of its input, you need to give it a generic function signature;如果您希望resolveObject()的 output 的类型取决于其输入的类型,则需要给它一个通用的 function 签名; there's no facility in TypeScript to just infer that a function has such a generic signature. TypeScript 中没有任何工具可以推断出 function 具有这样的通用签名。

The intended signature is something like this:预期的签名是这样的:

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

That means the output type is Promise<T> for some generic type T determined by the input obj .这意味着 output 类型是Promise<T>对于由输入obj确定的某些泛型类型T In particular, obj is of a mapped type where each property of T at key K , namely T[K] , is either left alone ( T[K] ), or (the union | ) wrapped in Promise ( Promise<T[K]> ).特别是, obj是一个映射类型,其中T在键K的每个属性,即T[K] ,要么单独存在( T[K] ),要么( 联合| )包装在PromisePromise<T[K]> )。 It turns out that the compiler is able to figure out T from the type of obj via inference from mapped types .事实证明,编译器能够通过映射类型的推断从obj的类型中找出T


Unfortunately, there's not much hope that the compiler would be able to follow the particular implementation you've got in order to verify that the returned value conforms to the type signature.不幸的是,编译器能够遵循您获得的特定实现以验证返回值是否符合类型签名的希望并不大。 The typings for Object.fromEntries() and Object.keys() are not specific enough to infer what you want, and even if it could, the correlation between the index i of Object.keys() and Object.values() is not easily representable. The typings for Object.fromEntries() and Object.keys() are not specific enough to infer what you want, and even if it could, the correlation between the index i of Object.keys() and Object.values() is not容易表示。 Instead of trying to figure out how to get the compiler to understand that the implementation is type safe, it is more expedient to just be very careful that we've done it right and then assert that we have done so:与其试图弄清楚如何让编译器理解实现是类型安全的,不如非常小心地确保我们做得对,然后断言我们已经这样做了:

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

We've asserted that Object.fromEntries(...) results in a value of type T .我们已经断言Object.fromEntries(...)导致T类型的值。 If we made a mistake then the compiler won't notice;如果我们犯了错误,那么编译器不会注意到; type assertions shift the burden of maintaining type safety from the compiler to the developer.类型断言将维护类型安全的负担从编译器转移到了开发人员身上。 But in this case, we have a single type assertion in the implementation of resolveObject() , and callers don't need to worry about it:但是在这种情况下,我们在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;
}> */

You can see that expectedOutput 's type has been inferred by the compiler to be what you expect: a Promise<> of an object type with known keys and property types:您可以看到expectedOutput的类型已被编译器推断为您所期望的:具有已知键和属性类型的 object 类型的Promise<>

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

Looks good.看起来不错。

Playground link to code Playground 代码链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM