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]; };
逐行解释
[K in keyof T]:遍历原类型T的所有属性键T[K] extends (...args: infer Args) => infer R:判断当前属性是不是函数,如果是,提取它的参数类型(Args)和返回类型(R)(...args: Args) => Promise<Awaited<R>>:生成一个新函数,参数和原函数完全一致,返回值改为Promise包裹的类型。这里用Awaited<R>是为了处理原函数已经返回Promise的情况——比如原返回Promise<string>,Awaited<R>会解包成string,再包裹Promise后还是Promise<string>,不会出现嵌套的Promise<Promise<string>>: 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
相关产品推荐
相关产品推荐

