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

RFC库开发:Proxy包装接口后类型系统未识别函数返回Promise问题

解决Proxy包装后函数返回类型的映射问题

我明白你遇到的痛点:你的RFC库中,客户端通过Proxy包装服务器端接口后,所有函数都会返回Promise,但TypeScript类型系统没法自动识别这个变化,导致类型提示和校验出问题。别担心,我们可以通过自定义映射类型来统一修正所有函数的返回类型,让类型系统和实际运行行为保持一致。

核心思路

我们需要创建一个映射类型,遍历原接口的所有属性:

  • 如果是函数,不管它原本返回什么(普通值还是Promise),都把它的返回类型包裹成Promise
  • 如果是非函数属性,保持原类型不变

实现映射类型

直接上代码,这个PromisifyAll类型就是关键:

type PromisifyAll<T> = {
  [K in keyof T]: T[K] extends (...args: infer Args) => infer R
    ? (...args: Args) => Promise<Awaited<R>>
    : T[K];
};

逐行解释

  1. [K in keyof T]:遍历原类型T的所有属性键
  2. T[K] extends (...args: infer Args) => infer R:判断当前属性是不是函数,如果是,提取它的参数类型(Args)和返回类型(R)
  3. (...args: Args) => Promise<Awaited<R>>:生成一个新函数,参数和原函数完全一致,返回值改为Promise包裹的类型。这里用Awaited<R>是为了处理原函数已经返回Promise的情况——比如原返回Promise<string>,Awaited<R>会解包成string,再包裹Promise后还是Promise<string>,不会出现嵌套的Promise<Promise<string>>
  4. : T[K]:如果当前属性不是函数,直接保留原类型

实际使用示例

假设你的服务器端接口类型是这样的:

// 原服务器端接口定义
interface ServerApi {
  // 原本就返回Promise的函数
  fetchUser(userId: number): Promise<{ id: number; name: string }>;
  // 返回普通值的函数
  calculateTotal(prices: number[]): number;
  // 返回普通对象的函数
  getAppConfig(): { theme: string; lang: string };
  // 非函数属性
  apiVersion: string;
}

我们用PromisifyAll生成Proxy包装后的类型:

// 生成Proxy后的接口类型
type ProxiedServerApi = PromisifyAll<ServerApi>;

然后在创建Proxy的时候,把实例的类型指定为ProxiedServerApi:

// 模拟Proxy包装逻辑
function createApiProxy(): ProxiedServerApi {
  return new Proxy({} as ServerApi, {
    get(target, prop) {
      if (typeof prop === 'string') {
        // 模拟后台HTTP调用逻辑
        return (...args: any[]) => {
          return fetch(`/api/${prop}`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(args),
          }).then(res => res.json());
        };
      }
      return Reflect.get(target, prop);
    },
  }) as ProxiedServerApi;
}

现在使用这个Proxy实例的时候,类型系统就会正确识别所有函数都返回Promise了:

const api = createApiProxy();

// 类型提示:Promise<{ id: number; name: string }>
api.fetchUser(1001).then(user => console.log(user.name));

// 类型提示:Promise<number>(原本返回number,现在自动转为Promise)
api.calculateTotal([10, 20, 30]).then(total => console.log(total));

// 类型提示:Promise<{ theme: string; lang: string }>
api.getAppConfig().then(config => console.log(config.theme));

// 非函数属性类型不变:string
console.log(api.apiVersion);

额外说明

  • 这个映射类型会保留原属性的修饰符,比如如果原接口有可选属性optionalMethod?: () => void,映射后会变成optionalMethod?: () => Promise<void>
  • 如果需要处理更复杂的函数类型(比如重载函数),可以稍微调整条件类型,但绝大多数场景下上面的实现已经足够用

内容的提问来源于stack exchange,提问作者WaeCo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 08:26:53