typescript - hvordan bruke prototypen til en PromiseConstructorLike-instans i grensesnittsdeklarasjon?


Jeg prøver å skrive deklarasjoner for Google Maps-hjelpemodulen for node, men jeg har problemer med PromiseConstructorLike som biblioteket forventer, og returnerer dens "PromiseLike" instansmetoder riktig (i henhold til https://googlemaps.github.io/google-maps-services-js/docs/module-@google_maps.html):

Promise     function    <optional>  Promise constructor (optional).

så jeg gjorde det (skrellet ned til de interessante bitene):

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
}

selvfølgelig fungerer det ikke, hvis jeg for eksempel bruker Bluebird i createClient som

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 */

Spørsmålet er da:

Er det noen måte jeg kan antyde at asPromise-metoden skal returnere instansmetodene (then, catch, finally, reduce, timeout, osv.) fra Bluebird uten å måtte utvide RequestHandle-grensesnittet manuelt?

Mer info (lib.d.ts deklarasjoner):

PromiseConstructorLike er:

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

PromiseLike er:

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

Svar #1

Dine deklarasjoner inneholder en kompilasjonsfeil, som stammer fra forveksling av Promise instans typen og Promise konstruktør typen. Typeparameteren T i GoogleMapsClient brukes for å fylle T i RequestHandle, men i GoogleMapsClient representerer dette Promise-konstruktørtypen, mens i RequestHandle representerer det Promise-instanstypen.

Det ser ut til å være din intensjon å gjøre alt ordentlig typet når det gjelder Promise instans type, PromiseLike<Response<U>>, hvor U er responstypen. Imidlertid, siden U ikke er kjent på forhånd (dvs. før man kaller GoogleMapsClient.directions), er dette dessverre ikke mulig.

Hvis du vil kalle then() etter asPromise(), kan du enkelt endre returtypen til RequestHandle.asPromise til PromiseLike<Response<U>> og fjerne typeparameteren T:

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

Jeg vil personlig også legge til begrensningen extends PromiseConstructorLike til typeparameteren T i både CreateClientOptions og GoogleMapsClient, slik at typesikkerheten til den overførte Promise-konstruktøren ikke kun avhenger av begrensningen spesifisert i createClient.

For å oppsummere ser deklarasjonene nå slik ut:

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
}

Med disse erklæringene fungerer ditt bluebird-eksempel, og du kan kalle then() etter asPromise().

Svar #2

med lanseringen av Typescript 2.8 gjør det nye "infer"-nøkkelordet dette mulig! Det kan antyde (og videreformidle) intrikate og nestede deklarasjoner som tolken vil prøve å hente informasjonen ut av for deg, og gi en virkelig god sterkt typet opplevelse.

så, hvis du vil få typen på konstruktøren

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(() => {})

den nye InstanceType er faktisk tilgjengelig i lib.es5.d.ts og er definert som:

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

det viser den virkelige kraften til infer-nøkkelordet, og du kan prøve det på https://www.typescriptlang.org/play/

Relaterte artikler