You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Proxy包装后类方法返回类型丢失的TypeScript类型推断问题

TypeScript错误捕获包装器的类型推断修复方案

问题背景

尝试实现一个错误捕获包装器,让类方法调用后返回[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);
    // 也支持调用异步函数
    // const result = await something(params)
    if (!result) throw "custom error";
    return result;
  }
}

调用const result = new Foo().boo("any")时,预期类型为Promise<[Awaited<ReturnType<typeof something>> | undefined, string | undefined]>,但实际推断类型为Promise<Awaited<ReturnType<typeof something>>>,只能手动指定类型解决,需要修复类型推断问题。

问题原因

原wrapCatch函数的泛型仅约束输入对象类型,但没有对输出的Proxy对象类型进行精确映射——TypeScript无法自动推断出Proxy拦截后方法返回值的类型变化,需要手动定义类型转换逻辑,将原对象的方法类型转换为包装后的元组Promise类型。

修复方案

通过映射类型重新定义wrapCatch的返回类型,将原对象中的每个方法转换为返回Promise<[Awaited<ReturnType<Fn>> | undefined, string | undefined]>的异步函数,非方法属性保持原类型:

// 定义包装后的方法类型
type WrappedMethod<Fn extends (...args: any[]) => any> = 
  (...args: Parameters<Fn>) => Promise<[Awaited<ReturnType<Fn>> | undefined, string | undefined]>;

// 定义包装后的对象类型:方法替换为WrappedMethod,非方法保持原类型
type WrappedObject<T> = {
  [K in keyof T]: T[K] extends (...args: any[]) => any 
    ? WrappedMethod<T[K]> 
    : T[K];
};

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[] = []): WrappedObject<T> =>
  new Proxy(instance, {
    get(target, prop, receiver) {
      const original = Reflect.get(target, prop, receiver);
      const propStr = prop as string;
      if (typeof original !== 'function' || excludes.includes(propStr))
        return original;
      // 约束original为函数类型
      const fn = original as (...args: any[]) => any;
      return async (...args: Parameters<typeof fn>) => {
        return _catch(() => fn.apply(target, args));
      } as WrappedMethod<typeof fn>;
    },
  }) as WrappedObject<T>;

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

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

  async boo(params: string) {
    const result = something(params);
    // 支持异步调用
    // const result = await something(params);
    if (!result) throw "custom error";
    return result;
  }
}

// 现在类型推断正确
const result = new Foo().boo("any");
// result的类型为:Promise<[string | undefined, string | undefined]>

关键改进点

  1. WrappedMethod类型:将原方法的参数和返回值转换为包装后的异步元组类型,使用Awaited处理原方法的Promise返回值。
  2. WrappedObject类型:通过映射类型遍历原对象的所有属性,仅替换方法类型,非方法属性保持原样。
  3. wrapCatch返回类型约束:明确指定返回WrappedObject<T>,让TypeScript正确推断包装后对象的方法类型。

内容的提问来源于stack exchange,提问作者최도영

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 11:01:04