我正在尝试创建一个包装器来捕获错误并返回
[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;
}
}
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]
但我不知道是否值得这么麻烦。