首页 > 解决方案 > 泛型函数的对象

问题描述

我试图让这个打字稿工作,但我不知道如何键入openPromises对象以使resolve函数没有错误,因为它输入错误。我看到的错误是:

Type '(value?: B | PromiseLike<B> | undefined) => void' is not assignable to type '<C>(value?: C | undefined) => void'.
    Types of parameters 'value' and 'value' are incompatible.
        Type 'C | undefined' is not assignable to type 'B | PromiseLike<B> | undefined'.
            Type 'C' is not assignable to type 'B | PromiseLike<B> | undefined'.
                Type 'C' is not assignable to type 'PromiseLike<B>'.

一个简单的解决方法是键入openPromisesas的值any,但希望在此基础上进行改进并在此过程中了解更多关于复杂泛型类型的信息。

这是我的代码,它证明了我的问题。我的来源要复杂得多,但这是我可以创建的证明问题的最低限度

interface DefaultResponse {
    a: 'a'
}

const openPromises: {
    [id: string]: { resolve<C>(value?: C): void }
} = {};

function putInObject<B extends DefaultResponse>(id: string) {
    return new Promise<B>(resolve => {
        openPromises[id] = { resolve }; 
    }) 
}

// ----------

interface MyResponse1 extends DefaultResponse {
    AA: string;
}

putInObject<MyResponse1>("test1").then(console.log);

const response1: MyResponse1 = { a:'a', AA: "test" };
openPromises["test1"].resolve(response1)


interface MyResponse2 extends DefaultResponse {
    BB: string;
}

putInObject<MyResponse2>("test2").then(console.log);

const response2: MyResponse2 = { a:'a', BB: "test" };
openPromises["test2"].resolve(response2)

您可以在此打字稿代码沙箱的左侧看到错误。谢谢你的帮助!

标签: typescript

解决方案


问题是您尝试做的并不是真正的类型安全。泛型函数是具有由调用者决定的类型参数的函数。在您的情况下,调用者只能传入一个有效的类型,即在putInObject为特定 ID 调用时确定的类型。因此,例如,这是允许的,即使它无效:

putInObject<MyResponse1>("test1").then(r => console.log(r.AA)); // expecting r to be MyResponse1
openPromises["test1"].resolve({ a:'a', BB: "test" }) // I can pas in anything since resolve is generic

没有一种openPromises真正类型安全的预先输入的好方法。由于每个键只有一个有效类型,并且这些键是稍后添加的,因此我们不能真正使用类型来openPromise模拟这种行为。

我能想到两种解决方法。

改变原始对象的类型

Typescript 不允许我们在声明变量后更改它的类型,但是我们会从putInObject相同的对象返回但类型不同,然后使用这个新对象来代替。下面,我使用了一个类来保持当前的类型openPromisses

class OpenPromisesManager<T = {}> {
  readonly openPromises: T = {} as T;
  putInObject<B extends DefaultResponse>(){
    return <TKey extends string>(id: TKey, withPromise: (p:Promise<B>) => void): OpenPromisesManager<T & Record<TKey, { resolve(value?: B): void }>> =>  {
      const newThis = this as any ;
      withPromise(new Promise<B>(resolve => {
          newThis.openPromises[id] = { resolve }; 
      }));
      return newThis as OpenPromisesManager<T & Record<TKey, { resolve(value?: B): void }>>;
    }
  }
}

// ----------

interface MyResponse1 extends DefaultResponse {
  AA: string;
}
const mgr = new OpenPromisesManager()
const mgr2 = mgr.putInObject<MyResponse1>()("test1", p=> p.then(r=> console.log(r.AA)));

const response1: MyResponse1 = { a:'a', AA: "test" };
mgr2.openPromises["test1"].resolve(response1)
mgr2.openPromises["test1"].resolve({ a:'a', BB: "test" }) // error

使用捕获请求类型的键

另一种选择是拥有一个在其类型中保持承诺的预期返回类型的键。这允许我们创建一个get函数,该函数将返回一个具有适当resolve类型的对象。

const openPromises: unknown = {};

type Key<T> = string & { __type: T }
function key<T>(k: string) {
  return k as Key<T>;
}
function putInObject<B extends DefaultResponse>(id: Key<B>) {
  return new Promise<B>(resolve => {
    (openPromises as any)[id] = { resolve }; 
  }) 
}

function get<B>(id: Key<B>): { resolve(value?: B): void } {
  return (openPromises as any)[id];
}

// ----------

interface MyResponse1 extends DefaultResponse {
  AA: string;
}

const test1 = key<MyResponse1>("test1");
putInObject<MyResponse1>(test1).then(console.log);

const response1: MyResponse1 = { a:'a', AA: "test" };
get(test1).resolve(response1)
get(test1).resolve({ a: 'a', BB: ''}) // error

推荐阅读