通用 "promisify"函数中的 Typescript 类型推断

标签 typescript type-inference typescript-typings

上下文

最近我正在研究第三方库的“promisification”。基本上,该库充满了 NodeJS 异步风格函数(使用回调作为最后一个参数)。 IE。具有与此类似的签名的函数:

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);
}

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

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仔细观察,你会发现这个解决方案并没有真正通用。这意味着,如果我需要 promise 一个具有 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);
    });
  }
}

我有一种感觉,我正在寻找的东西无法实现,这就是为什么长重载列表是作者不得不使用的最后手段/妥协解决方案。

最佳答案

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

已上roadmap有一段时间,在可变参数类型下。

关于通用 "promisify"函数中的 Typescript 类型推断,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46552920/

相关文章:

javascript - Angular2 在组件内部调用外部 JS 文件函数

compiler-construction - 将许多 typescript 文件转换为 1 个 js 文件,同时还使用 commonJS 模块

node.js - Nestjs:导入模块未定义,但可以导入模块中的方法和函数

javascript - 以 Angular 创建按钮时出错

generics - 如何使用未出现在第一个参数列表中的类型参数来改进 Scala 的类型推断?

types - D 自动类型错误(64 位问题?)

java - 如何使用 Stream.of 创建一个只有一个元素的 Stream<String[]>?

javascript - 打字不起作用 (TypeScript)

Typescript:具有至少一个 T 类型属性的对象

npm @types org 包中的 TypeScript 类型