首页 > 解决方案 > 打字稿:具有来自数组值的键的动态对象

问题描述

我已经创建了一个类来批量处理承诺并根据给定的键返回结果。例如,如果你给它两个名为orderand的键customer,每个键都有一个承诺,它将解析这些承诺并返回一个对象这些键作为属性,解析的值作为它们各自的值。

所以这里是如何使用这个类:

const batchPromiseHandler = new BatchPromise();

// getCustomerInfo and getPaymentInfo will give back a promise which resolves into their data

batchPromiseHandler.add('order', getOrderInfo());
batchPromiseHandler.add('customer', getCustomerInfo());

// await to resolve all into result object
const result = await batchPromiseHandler.resolveAll();

console.log(result.order);  // <<-- I want to be able to get suggestion order or customer from IDE
console.log(result.customer); 

这是实际的实现:

type resultDecorator = (data: any[], index: number) => any;

class BatchPromise {
  private promiseList: Promise<any>[] = [];
  private keyList: string[] = [];
  private decoratorList: resultDecorator[] = [];

  add(key: string, promise: Promise<any>, decorator?: resultDecorator): void {
    if (this.keyList.indexOf(key) !== -1) {
      throw new Error(`Key: "${key}" already exists in PromiseLand!`);
    }

    this.promiseList.push(promise);
    this.keyList.push(key);
    this.decoratorList.push(decorator);
  }

  async resolveAll(): Promise<{ [key: string]: any }> {   //    <<------ here is naive return type
    const resolvedArray = await Promise.all(this.promiseList);
    const result = {};

    for (let index = 0; index < this.promiseList.length; index++) {
      const key = this.keyList[index];

      result[key] =
        typeof this.decoratorList[index] === 'function'
          ? await this.decoratorList[index](resolvedArray[index], index)
          : resolvedArray[index];
    }

    return result;
  }
}

它按预期工作正常,但我希望能够自动完成resolveAll函数的结果。我不知道如何使用语言的动态类型特性,所以我只是这样做了:

Promise<{ [key: string]: any }>

我如何重构它以便能够获得例如IDEordercustomer向我提出的建议?

标签: typescriptclass

解决方案


这里的问题是该类型BatchPromise对它所持有的特定键和值一无所知。如果您希望它跟踪这一点,它需要是一个泛型类型,例如BatchPromise<T>,其中T表示返回的键值映射的对象类型resolveAll()

class BatchPromise<T extends object = {}> { 
  ... 
  async resolveAll(): Promise<T> { ... }
}

因此,每次调用时add(),您都会从 a 更改BatchPromise<T>BatchPromise<T & Record<K, V>>where ,K并且V分别是您的键和值类型。这有点麻烦:类型系统不支持任意更改现有对象的类型。如果你很小心,你可以BatchPromise这样写,这add()被视为缩小了类型,这受支持的;这将需要使用断言函数(以便add返回asserts this is BatchPromise<T & Record<K, V>>)。但是断言函数现在不是很容易使用,(参见microsoft/TypeScript#33622),所以我不打算提供这样的解决方案。

如果返回修改后类型的对象,则类型系统中的事情会变得更好,而不是让bp.add()方法更改 的类型:bpbp.add()BatchPromise

  add<K extends string, V>(
    key: K, promise: Promise<V>, decorator?: resultDecorator
  ): BatchPromise<T & Record<K, V>> { 
    ... 
    return this as BatchPromise<T & Record<K, V>>;
  }

为了使它起作用,您需要更改调用add()方式以合并方法链接而不是多个语句:

const batchPromiseHandler = new BatchPromise()
  .add('order', getOrderInfo())
  .add('customer', getCustomerInfo());

这样你batchPromiseHandler的类型就会像BatchPromise<{order: OrderInfo, customer: CustomerInfo}>.


让我们看看这是否有效:

const result = await batchPromiseHandler.resolveAll();
result.customer; // CustomerInfo
result.order; // OrderInfo
result.randomThing; // error!
// Property 'randomThing' does not exist on type 
// 'Record<"order", OrderInfo> & Record<"customer", CustomerInfo>'

看起来不错。您可以验证(通过下面的 Playground 链接)IDE 的 IntelliSense 将能够提示result具有 acustomer和 aorder属性。


好的,希望能给你一个前进的方向。祝你好运!

Playground 代码链接


推荐阅读