Как использовать прототип экземпляра 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>;
}
Ответы
Ответ 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()
.