Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to use the prototype of PromiseConstructorLike instance in interface declaration?

I'm trying to write declarations for Google Maps helper module for node, but I'm having problems with the PromiseConstructorLike that the library expects, and return it's "PromiseLike" instance methods properly (according to https://googlemaps.github.io/google-maps-services-js/docs/module-@google_maps.html):

Promise     function    <optional>  Promise constructor (optional).

so I did (stripped down to the interesting bits):

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
}

of course it doesn't work, if I use for example, Bluebird in the createClient as

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

The question is then:

Is there anyway I can hint the asPromise method to return the instance methods (then, catch, finally, reduce, timeout, etc) from bluebird without having to extend the RequestHandle interface manually?

More info (lib.d.ts declarations):

PromiseConstructorLike is:

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

PromiseLike is:

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>;
}
like image 348
pocesar Avatar asked Nov 08 '16 07:11

pocesar


1 Answers

Your declarations contain a compile error, which stems from confusing the Promise instance type and the Promise constructor type. Type parameter T in GoogleMapsClient is used to fill T in RequestHandle, however in GoogleMapsClient this represents the Promise constructor type, while in RequestHandle it represents the Promise instance type.

It appears to be your intention to make everything properly typed in terms of the Promise instance type, PromiseLike<Response<U>>, where U is the response type. However, since U is not known in advance (i.e. before calling GoogleMapsClient.directions), this is unfortunately not possible.

If you want to call then() after asPromise(), you can simply change the return type of RequestHandle.asPromise to PromiseLike<Response<U>> and remove the type parameter T:

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

I would personally also add the constraint extends PromiseConstructorLike to type parameter T in both CreateClientOptions and GoogleMapsClient, so that the type-safety of the passed Promise constructor does not solely depend on the constraint specified in createClient.

To summarize, the declarations now look like this:

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
}

With these declarations, your bluebird example works and you can call then() after asPromise().

like image 178
The Slicer Avatar answered Oct 21 '22 23:10

The Slicer