【问题标题】:how to use the prototype of PromiseConstructorLike instance in interface declaration?如何在接口声明中使用 PromiseConstructorLike 实例的原型?
【发布时间】:2017-03-21 18:26:17
【问题描述】:

我正在尝试为节点的 Google Maps 帮助程序模块编写声明,但我遇到了库期望的 PromiseConstructorLike 问题,并正确返回它的“PromiseLike”实例方法(根据https://googlemaps.github.io/google-maps-services-js/docs/module-@google_maps.html):

Promise     function    <optional>  Promise constructor (optional).

所以我做了(精简到有趣的部分):

declare namespace GoogleMaps {
  export interface CreateClientOptions<T> {
    /** Promise constructor (optional). */
    Promise?: T; 
  }

  export interface GoogleMapsClient<T> {
    directions<U>(query, callback?: ResponseCallback<U>): RequestHandle<U, T>;
  }

  export interface Response<U extends any> {
      headers: any;
      json: U;
      status: number;
  }

  export interface RequestHandle<U, T extends PromiseLike<Response<U>>> {
      asPromise(): T;
      cancel(): void;
      finally(callback: ResponseCallback<U>): void;
  }

  export type ResponseCallback<U> = (err: Error, result: Response<U>) => void; 
  export function createClient<T extends PromiseConstructorLike>(options: CreateClientOptions<T>): GoogleMapsClient<T>;
}

declare module '@google/maps' {
  export = GoogleMaps
}

当然不行,比如我用createClient中的Bluebird作为

import * as bluebird from 'bluebird'
import { createClient } from '@google/maps'

createClient({ Promise: bluebird }).directions({}).asPromise()/** no "then" here, just the static methods from Bluebird, like Bluebird.all */

那么问题来了:

我是否可以提示 asPromise 方法从 bluebird 返回实例方法(然后,catch,finally,reduce,timeout 等),而无需手动扩展 RequestHandle 接口?强>

更多信息(lib.d.ts 声明):

PromiseConstructorLike 是:

 declare type PromiseConstructorLike = new <T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void) => PromiseLike<T>;

PromiseLike 是:

interface PromiseLike<T> {
    /**
     * Attaches callbacks for the resolution and/or rejection of the Promise.
     * @param onfulfilled The callback to execute when the Promise is resolved.
     * @param onrejected The callback to execute when the Promise is rejected.
     * @returns A Promise for the completion of which ever callback is executed.
     */
    then(
        onfulfilled?: ((value: T) => T | PromiseLike<T>) | undefined | null,
        onrejected?: ((reason: any) => T | PromiseLike<T>) | undefined | null): PromiseLike<T>;
}

【问题讨论】:

    标签: typescript typescript-typings typescript2.0


    【解决方案1】:

    您的声明包含编译错误,这是由于混淆了 Promise instance 类型和 Promise constructor 类型。 GoogleMapsClient中的类型参数T用于填充RequestHandle中的T,但是在GoogleMapsClient中这表示Promise构造函数类型,而在RequestHandle中表示Promise实例类型。

    您似乎打算根据Promise instance 类型PromiseLike&lt;Response&lt;U&gt;&gt; 正确键入所有内容,其中U 是响应类型。但是,由于事先不知道U(即在调用GoogleMapsClient.directions 之前),很遗憾这是不可能的。

    如果要在asPromise()之后调用then(),只需将RequestHandle.asPromise的返回类型改为PromiseLike&lt;Response&lt;U&gt;&gt;并去掉类型参数T即可:

    export interface RequestHandle<U> {
        asPromise(): PromiseLike<U>;
        cancel(): void;
        finally(callback: ResponseCallback<U>): void;
    }
    

    我个人还会在CreateClientOptionsGoogleMapsClient 中将约束extends PromiseConstructorLike 添加到类型参数T,以便传递的Promise 构造函数的类型安全不仅仅取决于指定的约束在createClient

    总而言之,声明现在如下所示:

    declare namespace GoogleMaps {
      export interface CreateClientOptions<T extends PromiseConstructorLike> {
        /** Promise constructor (optional). */
        Promise?: T; 
      }
    
      export interface GoogleMapsClient<T extends PromiseConstructorLike> {
        directions<U>(query, callback?: ResponseCallback<U>): RequestHandle<U>;
      }
    
      export interface Response<U extends any> {
          headers: any;
          json: U;
          status: number;
      }
    
      export interface RequestHandle<U> {
          asPromise(): PromiseLike<Response<U>>;
          cancel(): void;
          finally(callback: ResponseCallback<U>): void;
      }
    
      export type ResponseCallback<U> = (err: Error, result: Response<U>) => void; 
      export function createClient<T extends PromiseConstructorLike>(options: CreateClientOptions<T>): GoogleMapsClient<T>;
    }
    
    declare module '@google/maps' {
      export = GoogleMaps
    }
    

    通过这些声明,您的bluebird 示例可以正常工作,您可以在asPromise() 之后调用then()

    【讨论】:

    • 我不想要asPromise() 中的通用PromiseLike,我想要asPromise() 方法中可用的bluebird 函数(如我在问题中所述),例如catchmapeachbindspread 等使用 PromiseLike 破坏了在 createClient 中使用 PromiseConstructorLike 的目的
    • 我剥离了U,它实际上为每个方法扩展了一个对象,与query参数相同,所以人们会关注构造函数/实例问题
    • 据我所知,由于 Promise 的结果类型的类型参数,不可能创建完全类型安全的声明来实现您想要的。请注意,PromiseConstructorLike 中的 'new' 在 Promise 结果类型中也是泛型的,这意味着 PromiseConstructorLike 不能在它返回的 Promise 类型中成为泛型,这是解决此问题所必需的。
    • 在调用 'asPromise' 后,您可以做的一件事是让该方法本身在 Promise 实例类型中通用:'asPromise>(): T' .这确实需要在您调用此方法时指定正确的 Promise 类型,并且它也不是类型安全的,因为您基本上必须转换结果。请注意,这个技巧也可以应用于 'directions' 方法,然后可以像现在一样将 Promise 类型传递给 RequestHandle。
    • 有,有{ new(): T },但不能在这种情况下使用
    【解决方案2】:

    随着 Typescript 2.8 的发布,新的“infer”关键字使这成为可能!它可以推断(并传递)解释器会尝试为您提取信息的复杂嵌套声明,从而提供非常好的强类型化体验。

    所以,如果你想获取构造函数的类型

    class MyPromise extends Promise<any> implements PromiseLike<any> {
        add(s: number) {
            s++
            return this
        }
        dummy() {
            return this
        }
    }
    
    function typedFactory<
        U extends PromiseConstructorLike,
    >(u: U): InstanceType<U> {
        return new u<void>(() => { }) as any 
        // this isn't needed since we are just trying to show the functionality, 
        // would be interfacing another library through types only, so that 
        // the compiler doesn't b*tch about it
    }
    
    typedFactory(Promise).then(() => { })
    typedFactory(MyPromise).add(1).dummy().then(() => {})
    

    新的InstanceType实际上在lib.es5.d.ts中可用,定义为:

    type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;
    

    它显示了infer关键字的真正威力,您可以在https://www.typescriptlang.org/play/中尝试一下

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-10-27
      • 2020-03-13
      • 2017-04-21
      • 1970-01-01
      • 1970-01-01
      • 2022-10-21
      • 1970-01-01
      相关资源
      最近更新 更多