广义“promisify”函数中的Typescript类型推断

时间:2017-10-03 20:29:50

标签: typescript type-inference typescript-typings

上下文

最近,我正致力于第三方图书馆的“宣传”。基本上,库中充满了NodeJS异步样式函数(使用回调作为最后一个参数)。即签名与此类似的功能:

function foo(arg1: string, arg2: number, ..., callback: (error, result) => void): void

我尝试编写一个函数会减少包装原始函数的代码并使它们成为Promise<T>返回的函数:

function cb<TResult>(
  resolve: (res: TResult) => void,
  reject: (err: any) => void
): (actualError, actualResult) => void {

  return (error, result) => error ? reject(error) : resolve(result);
}

然后为了宣传这些方法,我会编写这样的代码:

patchUserMetadata(userId: string, userMetadata: any): Promise<a0.Auth0UserProfile> {
  return new Promise((resolve, reject) =>
    this.wrapped.patchUserMetadata(userId, userMetadata, cb(resolve, reject)));
}

linkUser(userId: string, secondaryUserToken: string): Promise<any> {
  return new Promise((resolve, reject) =>
    this.wrapped.linkUser(userId, secondaryUserToken, cb(resolve, reject)));
}

// ... and so on, and on, and on...

你可以很容易地看到,我对TypeScript还不是很熟悉,而且基本上是在试图重新发明一个轮子。我的轮子最后成了六角形,我手写包装代码太多了......

审核我的代码的人指出,我可以使用 js-promisify 以更低的成本获得类似的结果。该库定义了一个执行该任务的帮助程序:

module.exports = function (fun, args, self) {
  return new Promise(function (resolve, reject) {
    args.push(function (err, data) {
      err && reject(err);
      resolve(data);
    })
    fun.apply(self, args);
  });
};

由于我处理的是TypeScript而不是JavaScript,我进一步研究了一下。这就是我最终选择 typed-promisify 的方式,现在代码如下:

patchUserMetadata = promisify(this.wrapped.patchUserMetadata);

linkUser = promisify(this.wrapped.linkUser);

多整洁,呵呵?

越来越近了

我想知道这个promisify函数究竟是如何工作的?我查看了源代码,发现了一个类似于js-promisify的解决方案:

export function promisify<T>(f: (cb: (err: any, res: T) => void) => void, thisContext?: any): () => Promise<T>;
export function promisify<A, T>(f: (arg: A, cb: (err: any, res: T) => void) => void, thisContext?: any): (arg: A) => Promise<T>;
export function promisify<A, A2, T>(f: (arg: A, arg2: A2, cb: (err: any, res: T) => void) => void, thisContext?: any): (arg: A, arg2: A2) => Promise<T>;
// ...more overloads

export function promisify(f: any, thisContext?: any) {
  return function () {
    let args = Array.prototype.slice.call(arguments);
    return new Promise((resolve, reject) => {
      args.push((err: any, result: any) => err !== null ? reject(err) : resolve(result));
      f.apply(thisContext, args);
    });
  }
}

问题

如果仔细查看promisify,您会发现此解决方案并未真正推广。意思是,如果我需要使用10+参数来实现一个函数,那么就不会有匹配的重载。实现仍然可以正常工作,但在这种情况下类型信息会丢失。

TypeScript中是否有一种方法可以推断出精确的函数类型(或签名,或参数的数量和类型),而无需预先定义所有那些讨厌的重载?

我正在寻找类似的东西[显然,伪代码]:

export function promisify<...[TArgs], T>(
  f: (...allArgsButLastTwo: [TArgs],
  cb: (err: any, res: T) => void) => void,
  thisContext?: any
): (...[TArgs]) => Promise<T>;

export function promisify(
  ...allArgsButLastTwo: any[],
  f: any,
  thisContext?: any
) {
  return function () {
    let args = Array.prototype.slice.call(arguments);
    return new Promise((resolve, reject) => {
      args.push((err: any, result: any) => err !== null ? reject(err) : resolve(result));
      f.apply(thisContext, args);
    });
  }
}

我有一种感觉,我正在寻找的是而不是可以实现的,这就是为什么长过载列表是作者必须使用的最后手段/妥协解决方案。

1 个答案:

答案 0 :(得分:3)

从版本2.5开始,在解决此问题之前,目前无法在TypeScript中执行此操作: https://github.com/Microsoft/TypeScript/issues/5453

在Variadic Types下,它已经在roadmap上了一段时间。