代理包装后类方法返回类型丢失

问题描述 投票:0回答:1

我正在尝试创建一个包装器来捕获错误并返回

[result | undefined, error | undefined]
的元组。然而,TypeScript 类型推断并未按预期工作。 这是我的实现:

const _catch = async <T>(
  cb: () => Promise<T> | T
): Promise<[T | undefined, string | undefined]> => {
  try {
    const result = await cb();
    return [result, undefined];
  } catch (error) {
    if (error instanceof Error) throw error;
    return [undefined, error as string];
  }
};

const wrapCatch = <T extends object>(instance: T, excludes: string[] = []) =>
  new Proxy(instance, {
    get(target, prop, receiver) {
      const original = Reflect.get(target, prop, receiver);
      if (typeof original !== 'function' || excludes.includes(prop as string))
        return original;
      return async (...args: any[]) => {
        return _catch(() => (original as (...args: any[]) => any).apply(target, args));
      };
    },
  });

function something(n : any){
   return "hello"
}

class Foo {
  constructor() {
    return wrapCatch(this);
  }

  async boo(params: string) {
    const result = something(params);
    // I want to use async function also
    // const result = await something(params)
    if (!result) throw "custom error";
    return result;
  }
}

当我调用该方法时:

const result = new Foo().boo("any")

预期类型:

[Awaited<ReturnType<typeof something>> | undefined, string | undefined]

实际类型:

Awaited<ReturnType<typeof something>>

代理包装器应该修改包装方法的返回类型以包含错误元组,但 TypeScript 无法正确推断这一点。如何解决这种类型推断问题?

现在我通过强制给它这样的类型来编写它。

type A<T> = Promise<[T | undefined, string | undefined]>;

function something(n : any){
   return "hello"
}

class Foo {
    constructor() {
        return wrapCatch(this);
    }

    async boo(params): A<ReturnType<somthing>> {
        const result = something(params);
        if (!result) throw 'custom error';

        return result;
    }
}
typescript proxy
1个回答
0
投票

TypeScript 目前假定

Proxy
与其
target
具有相同的类型。 它无法推断
Proxy
实际上的行为与其他类型相同。 microsoft/TypeScript#20846 中存在一个现有问题,但除非实现了该问题,否则您需要手动断言
Proxy
的类型。

就你而言,它看起来像:

type WrapCatch<T extends object, K extends keyof T> = { [P in keyof T]:
  P extends K ? T[P] :
  T[P] extends (...args: infer A) => infer R ?
  (...args: A) => Promise<[Awaited<R> | undefined, string | undefined]> :
  T[P] }

const wrapCatch = <T extends object, K extends keyof T = never>(
  instance: T, excludes: K[] = []
) => new Proxy(instance, {
  get(target, prop, receiver) {
    const original = Reflect.get(target, prop, receiver);
    if (typeof original !== 'function' || (excludes as readonly PropertyKey[]).includes(prop))
      return original;
    return async (...args: any[]) => {
      return _catch(() => (original as (...args: any[]) => any).apply(target, args));
    };
  },
}) as WrapCatch<T, K>;

我将

WrapCatch<T, K>
定义为您在
wrapCatch(instance, excludes)
时想要看到的内容,其中
instance
属于 generic 类型
T
,而
excludes
属于泛型类型
K
constrained
T
的键,并且 defaults
never
,因此省略
excludes
与在类型级别指定
[]
相同。

它是一个映射类型,其中每个属性都是一个条件类型,它将键不在

K
中的任何函数包装到一个新函数中,该函数采用相同的参数,但将
Promise
返回到 tuple形状正确。


TypeScript 还假设

class
声明的实例类型与
this
相同。如果您在
constructor
中返回某些内容,它无法推断出不同的内容。 microsoft/TypeScript#38519 也存在此问题,但除非该问题已实现,否则您需要解决它。我建议不要从构造函数中进行
return
操作,而是使用一个包装类构造函数的函数。 像这样:

class _Foo {
  constructor() { }
  async boo(params: string) {
    const result = await something(params);
    if (!result) throw "custom error";
    return result;
  }
}

function Foo() {
  return wrapCatch(new _Foo());
}

const result = Foo().boo("any")
//    ^? const result: Promise<[string | undefined, string | undefined]>
result.then(x => console.log(x)) // ["ANY", undefined]

这里

_Foo
是你的底层类,你不会直接使用它。现在
Foo()
是一个常规函数,其函数体调用
wrapCatch(new _Foo())
,因此返回一个
WrapCatch<_Foo, never>
。你可以尝试使
Foo
成为构造函数而不是函数,也许可以通过某种类包装工厂混合来实现:

function wrapClass<A extends any[], T extends object, U extends object>(
  ctor: new (...a: A) => T,
  wrapFn: (t: T) => U): new (...a: A) => U {
  return class extends (ctor as any) {
    constructor(...args: A) {
      super(...args);
      return wrapFn(this as any);
    }
  } as any
}
const Foo = wrapClass(_Foo, wrapCatch);
// const Foo: new () => WrapCatch<_Foo, never>

const result = new Foo().boo("yna");
result.then(x => console.log(x)) // ["YNA", undefined]

但我不知道是否值得这么麻烦。

Playground 代码链接

© www.soinside.com 2019 - 2024. All rights reserved.