使用typescript中的回调来实现函数的Promisify

时间:2018-03-15 15:42:51

标签: typescript

我有一个接受回调的方法有多个重载的类。 我想改为以Promise为基础,但我在使用TypeScript泛型时以类型安全的方式执行此操作时遇到了麻烦。

基本上API就是这样:

class CallbackClient {
  method(req: Request, callback: (error: Error, response: Response) => void): void
  method(req: Request, options: Options, callback: (error: Error, response: Response) => void): void
}

应该成为这个:

class PromiseClient {
  method(req: Request): Promise<Response>
  method(req: Request, options: Options): Promise<Response>
}

但是如何在TypeScript中将PromiseClient定义为泛型类型? 在伪代码中,我想要一个这样的类型:

type PromiseClient<T> = {
  [P in keyof T]: (args of T[P] without the callback) => Promise<response of T[P] in callback>
}

我有一个实现可以实现这一点,但由于我无法实现这一点,因此我放弃了类型安全。

映射它的功能如下:

function toPromiseClient<T extends CallbackClient>(client: T): PromiseClient<T> {
  const promiseClient: PromiseClient<T> = {}
  Object.keys(Object.getPrototypeOf(client))
    .forEach((functionName: string) => {
      const originalFunction = client[functionName as keyof T]
      if (!originalFunction) {
        return
      }
      promiseClient[functionName as keyof T] = (...args: any[]) => new Promise<any>((resolve, reject) => {
        if (args && args.length > 1 && typeof (args[args.length - 1]) === 'function') {
          reject(new Error('Use Promise API instead of callbacks'))
          return
        }
        originalFunction.call(client, ...args, (error: Error, res: any) => {
          if (error) {
            reject(error)
            return
          }
          resolve(res)
        })
      })
    })
  return promiseClient
}

1 个答案:

答案 0 :(得分:2)

这在部分原型2.8中有部分可能(尚未发布,应该在2018年3月发布,如果通过npm install -g typescript@next则可以获得)

解决方案:

declare class CallbackClient {
    method1(req: Request, options: Options, callback: (error: Error, response: Response) => void): void
    method1(req: Request, callback: (error: Error, response: Response) => void): void


    method2(req: Request, options: Options, callback: (error: Error, response: Response) => void): void

    method3(req: Request, options?: Options, callback?: (error: Error, response: Response) => void): void
}

type Promisify<T> = {
    [P in keyof T]:
    T[P] extends (callback: (error: Error, response: infer TResponse) => void) => void ? () => Promise<TResponse> :
    T[P] extends (p1: infer P1, callback: (error: Error, response: infer TResponse) => void) => void ? (p1: P1) => Promise<TResponse> :
    T[P] extends (p1: infer P1, p2: infer P2, callback: (error: Error, response: infer TResponse) => void) => void ? (p1: P1, p2: P2) => Promise<TResponse> :
    T[P] extends (p1: infer P1, p2: infer P2, p3: infer P3, callback: (error: Error, response: infer TResponse) => void) => void ? (p1: P1, p2: P2, p3: P3) => Promise<TResponse> :
    never;
}

type PromiseClient = Promisify<CallbackClient>; 
// Will be equivalent to :
// type PromiseClient = {
//     method1: (p1: Request) => Promise<Response>; // Only one overload, not the second
//     method2: (p1: Request, p2: Options) => Promise<Response>;
//     method3: (p1: Request, p2: Options | undefined) => Promise<Response>;
// }

上面的解决方案使用条件类型从原始类的每个方法中提取参数类型,然后使用这些类型为返回promise的方法创建新的函数类型。

限制:

  1. 它不适用于任意数量的参数,上述解决方案最多可用于3个参数和回调,但您可以添加更多
  2. 参数名称丢失
  3. 如果参数是可选的,则其类型为ArgType | undefined
  4. 将成为必需参数
  5. 如果方法存在多个重载,则只有参数最小的方法才会将其作为结果。在上面的例子中,我们只得到method1: (p1: Request) => Promise<Response>我们也没有得到method1: (p1: Request, p2: Options) => Promise<Response>(并且根据重载的顺序,您可能会得到奇怪的结果,这很古怪)。