代码之家  ›  专栏  ›  技术社区  ›  Igor Soloydenko

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

  •  2
  • Igor Soloydenko  · 技术社区  · 7 年前

    最近,我正在研究第三方图书馆的“Promision”。基本上,库中充满了NodeJS异步风格的函数(使用回调作为最后一个参数)。一、 e.具有类似于此签名的功能:

    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...
    

    有人审查了我的代码,指出我可以使用 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);
        });
      }
    }
    

    问题

    如果你看看 仔细观察,你会发现这个解并不是真正的广义解。也就是说,如果我需要提供一个包含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 回复  |  直到 7 年前
        1
  •  4
  •   fs_    7 年前

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

    roadmap 有一段时间,在可变类型下。

        2
  •  0
  •   Acy    3 年前

    因为变量类型现在是 supported (TS 4.0+),我们可以写

    type callback<P, Q> = (error: P, response: Q) => void;
    const promisify =
      <T extends readonly unknown[], U, V>(
        f: (...fargs: [...T, callback<U, V>]) => void
      ) =>
      (...args: T) =>
        new Promise((res, rej) => {
          const callback: callback<U, V> = (err, response) => {
            if (err) rej(err);
            res(response);
          };
          f(...args, callback);
        });
    

    promisify 当应用于重载函数时,并不总是有效。示例:下面给出了一个 Expected 1 arguments, but got 2. 错误位于 'utf8' .

    const pmifyRead = promisify(fs.readFile); 
    pmifyRead('/Users/joe/test.txt', 'utf8').then(console.log).catch(console.error)
    

    但是 fs.readFile 可以在其回调参数之前接受两个参数!不幸的是,此函数有4个重载,并且在绑定到时选择了一个重载 承诺 是最底层的重载:只接收 path callback . 如果我可以将类型推断延迟到promisifid函数被调用,那么一切都应该工作。但是,类型在提出时已经推断出来。感谢所有有解决方案的人。