Подтвердить что ты не робот

Как использовать прототип экземпляра PromiseConstructorLike в объявлении интерфейса?

Я пытаюсь написать декларации для вспомогательного модуля Google Maps для node, но у меня возникают проблемы с PromiseConstructorLike, которые ожидают библиотеки, и правильно верните им методы экземпляра "PromiseLike" (в соответствии с https://googlemaps.github.io/google-maps-services-js/docs/[email protected]_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
}

Конечно, это не сработает, если я использую, например, Bluebird в createClient как

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, чтобы возвращать методы экземпляра (затем, catch, finally, сокращение, тайм-аут и т.д.) из bluebird без необходимости расширения интерфейса 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>;
}
4b9b3361

Ответ 1

с выходом 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/

Ответ 2

В ваших объявлениях содержится ошибка компиляции, связанная с запуском типа экземпляра Promise и типа конструктора Promise. Параметр типа T в GoogleMapsClient используется для заполнения T в RequestHandle, однако в GoogleMapsClient это представляет тип конструктора Promise, тогда как в RequestHandle он представляет тип экземпляра Promise.

Кажется, вы намерены сделать все правильно введенным с помощью типа экземпляра Promise, PromiseLike<Response<U>>, где U - тип ответа. Однако, поскольку U неизвестно заранее (т.е. До вызова GoogleMapsClient.directions), это, к сожалению, невозможно.

Если вы хотите вызывать then() после asPromise(), вы можете просто изменить возвращаемый тип RequestHandle.asPromise на PromiseLike<Response<U>> и удалить параметр типа T:

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

Я бы также добавил ограничение extends PromiseConstructorLike для ввода параметра T как в CreateClientOptions, так и в GoogleMapsClient, так что безопасность типа переданного конструктора 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 работает, и вы можете вызвать then() после asPromise().