如何在TypeScript中类型安全地将对象回调式方法Promise化
类型安全地Promise化回调风格对象(以gRPC客户端为例)
第一步:定义类型安全的Promise化映射类型
先通过TypeScript条件类型和类型推断,把回调风格的方法自动转换为返回Promise的类型,同时保留非方法属性和非回调风格方法的原类型:
// 定义Node.js标准回调类型:(错误, 结果)格式 type Callback<T> = (error: Error | null, result: T) => void; // 判断一个方法是否为回调风格(最后一个参数是Callback) type IsCallbackStyleMethod<T> = T extends (...args: [...rest: infer Rest, cb: Callback<infer R>]) => void ? true : false; // 将单个回调风格方法转换为Promise版本 type PromisifyMethod<T> = T extends (...args: [...rest: infer Rest, cb: Callback<infer R>]) => void ? (...args: Rest) => Promise<R> : T; // 递归处理嵌套对象,实现全对象的Promise化类型 type Promisified<T> = { [K in keyof T]: T[K] extends Function ? IsCallbackStyleMethod<T[K]> extends true ? PromisifyMethod<T[K]> : T[K] : T[K] extends object ? Promisified<T[K]> : T[K]; };
第二步:实现Proxy包装函数
用Proxy的get陷阱拦截属性访问,自动将回调风格方法包装为Promise,同时递归处理嵌套对象:
function promisify<T extends object>(obj: T): Promisified<T> { return new Proxy(obj, { get(target, prop: keyof T) { const value = target[prop]; // 处理回调风格方法:包装为Promise if (typeof value === 'function') { return (...args: any[]) => { return new Promise((resolve, reject) => { // 绑定原对象上下文,避免gRPC客户端内部逻辑出错 value.call(target, ...args, (err: Error | null, result: any) => { err ? reject(err) : resolve(result); }); }); }; } // 递归处理嵌套对象(如gRPC的嵌套服务) if (typeof value === 'object' && value !== null) { return promisify(value); } // 非函数/非对象属性直接返回 return value; }, }) as Promisified<T>; }
第三步:使用示例(模拟gRPC客户端)
模拟自动生成的gRPC客户端类型,验证类型安全和功能:
// 模拟自动生成的gRPC客户端类型 type AutogeneratedClient = { getUser: (userId: string, cb: Callback<{ id: string; name: string }>) => void; updateUser: (user: { id: string; name: string }, cb: Callback<{ success: boolean }>) => void; // 嵌套服务示例 adminService: { deleteUser: (userId: string, cb: Callback<{ deleted: boolean }>) => void; }; // 非回调风格的属性/方法保持原样 metadata: { apiVersion: string }; ping: () => void; }; // 创建模拟的gRPC客户端实例 const rawClient: AutogeneratedClient = { getUser: (userId, cb) => setTimeout(() => cb(null, { id: userId, name: 'Alice' }), 100), updateUser: (user, cb) => setTimeout(() => cb(null, { success: true }), 100), adminService: { deleteUser: (userId, cb) => setTimeout(() => cb(null, { deleted: true }), 100), }, metadata: { apiVersion: 'v1' }, ping: () => console.log('pong'), }; // Promise化客户端 const promisifiedClient = promisify(rawClient); // 类型安全地调用 async function testClient() { // 自动推断返回值类型,支持类型提示 const user = await promisifiedClient.getUser('123'); console.log(user.name); // 类型提示:string const updateResult = await promisifiedClient.updateUser({ id: '123', name: 'Bob' }); console.log(updateResult.success); // 类型提示:boolean // 嵌套服务同样支持Promise化和类型提示 const deleteResult = await promisifiedClient.adminService.deleteUser('123'); console.log(deleteResult.deleted); // 类型提示:boolean // 非回调方法/属性保持原功能和类型 promisifiedClient.ping(); console.log(promisifiedClient.metadata.apiVersion); // 输出:v1 } testClient();
关键注意事项
- 确保回调符合Node.js标准
(error: Error | null, result: T) => void格式,gRPC自动生成的客户端默认符合这个规范。 - 用
value.call(target)绑定原对象上下文,避免gRPC客户端内部逻辑因this丢失出错。 - 如果遇到非标准回调格式,只需调整
IsCallbackStyleMethod和PromisifyMethod的条件推断逻辑即可适配。
内容的提问来源于stack exchange,提问作者Kalina
相关产品推荐
相关产品推荐

