首页 > 解决方案 > 使用并发承诺填充记录

问题描述

示例代码:

type I = 'a' | 'b' | 'c';

const is: Record<I, null> = { 'a': null, 'b': null, 'c': null}

// replace with any async processing function of your choice
function asyncRandomize(): Promise<number> {
    return new Promise(resolve => setTimeout(() => resolve(Math.random()), 1000))
}

const irandomized: Promise<[I, number][]> = Promise.all(
    Object.keys(is).map((i: I) => 
        asyncRandomize()
            .then((v): [I, number] => [i, v])
    )
)

有没有办法让irandomized有类型Promise<Record<I, number>>而不是Promise<[I, number][]>不经过any

标签: typescript

解决方案


我认为您将需要一个Object.fromEntries()像那:Record<string, number>Record<I, number>

const irandomized  = Promise.all(
   (Object.keys(is) as I[]).map((i: I) =>
      asyncRandomize()
         .then((v): [I, number] => [i, v])
   )
).then(z => Object.fromEntries(z)) as Promise<Record<I, number>>;

你没有any在那里使用。


如果您不想在代码中使用类型断言,则可以创建自己的函数,就像Object.fromEntries()其类型更具体一点(尽管类型断言等需要在该函数的实现中发生)。这是一个可能对您有用的功能:

type Entry = readonly [PropertyKey, any];
type ExtractSupertype<T, U> = T extends any ? [U] extends [T] ? T : never : never;
function fromEntries<E extends readonly Entry[]>(entries: E): {
   [K in E[number][0]]: ExtractSupertype<E[number], readonly [K, any]>[1] // TS 4.0-
   // [K in E[number] as K[0]]: K[1] // TS 4.1+, easier syntax
} {
   const ret: any = {};
   for (let entry of entries) {
      ret[entry[0]] = entry[1];
   }
   return ret;
}

请注意,当 TypeScript 4.1 发布并引入映射类型as子句ExtractSupertype时,将不再需要该公式。

你可以把它藏在图书馆的某个地方。这个想法是fromEntries()应该能够将强类型数组或条目元组转换为强类型对象:

const foo = fromEntries([
   ["x", Math.random()], ["y", new Date()], ["z", Math.random() < 0.5]
] as const);
/* const foo: {
    x: number;
    y: Date;
    z: boolean;
} */

您可以看到Record<string, number | Date | boolean>,您实际上获得了与特定类型关联的特定属性,而不是仅仅获取 。

有了这个,你可以在没有更多不安全类型断言的情况下做这样的事情:

const iKeys = ["a", "b", "c"] as const;
const irandomized = Promise.all(
   iKeys.map((k) =>
      asyncRandomize()
         .then(v => [k, v] as const)
   )
).then(fromEntries);

请注意,我is只更改为它的键数组。无论如何,您并没有对其值做任何事情,并且编译器将Object.keys(is)其视为string[]而不是I[](并且有充分的理由,请参阅this question),因此我选择删除中间人并改用强类型的键元组。

无论如何,您可以验证这irandomized是您期望的类型:

/* const irandomized: Promise<{
    a: number;
    b: number;
    c: number;
}> */

并且它在运行时按预期工作:

irandomized.then(e => console.log(JSON.stringify(e)));
// {"a":0.9961594084980729,"b":0.015675814053288217,"c":0.1783156372032898}

Playground 代码链接


推荐阅读