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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 13:45:14