0
votes

I’m writing definitions for es6-promise-pool to add to DefinitelyTyped, with some tweaks from the discussion at GitHub. The library can take an optional promise parameter that specifies a class to use for the returned promise (e.g. ES6-Promise’s polyfill or Bluebird). Internally, all it cares about is that you can call new Promise(…), so this is enough to specify the type of the value:

interface PromiseClass<A, P extends PromiseLike<A>> {
    new(callback: (resolve: (value?: A | P) => void, reject: (reason?: any) => void) => void): P;
}

const bar: PromiseClass<number, Bluebird<number>> = Bluebird; // OK

Where things get hairy is in specifying the type of the options:

interface Options<A, P extends PromiseLike<A>, C extends PromiseClass<A, P>> {
    promise?: C;
}

function foo<A, P extends PromiseLike<A>, C extends PromiseClass<A, P>>(options: Options<A, P, C>) { /* empty */ }

foo({ promise: Bluebird.resolve(3) });

This gives me an error:

Argument of type '{ promise: Bluebird; }' is not assignable to parameter of type 'Options, PromiseClass>>'.
  Types of property 'promise' are incompatible.
    Type 'Bluebird' is not assignable to type 'PromiseClass> | undefined'.
      Type 'Bluebird' is not assignable to type 'PromiseClass>'.
        Type 'Bluebird' provides no match for the signature 'new (callback: (resolve: (value?: {} | PromiseLike | undefined) => void, reject: (reason?: any) => void) => void): PromiseLike'.

(Note that the type is inferred as {}.)

If I manually specify the types:

foo<number, Bluebird<number>, PromiseClass<number, Bluebird<number>>>({ promise: Bluebird.resolve(3) });

I get a similar error:

Argument of type '{ promise: Bluebird; }' is not assignable to parameter of type 'Options, PromiseClass>>'.
  Types of property 'promise' are incompatible.
    Type 'Bluebird' is not assignable to type 'PromiseClass> | undefined'.
      Type 'Bluebird' is not assignable to type 'PromiseClass>'.
        Type 'Bluebird' provides no match for the signature 'new (callback: (resolve: (value?: number | PromiseLike | undefined) => void, reject: (reason?: any) => void) => void): Bluebird'.

Here are the relevant definitions from DefinitelyTyped, which ought to be compatible when the type is inferred correctly:

type Resolvable<R> = R | PromiseLike<R>;

constructor(callback: (resolve: (thenableOrResult?: Resolvable<R>) => void, reject: (error?: any) => void, onCancel?: (callback: () => void) => void) => void);

For context, here’s how I’m using the type (unrelated methods omitted):

declare class PromisePool<
    A,                        // the type of value returned by the source
    P extends PromiseLike<A>, // the type of Promise returned by the source
    P2 extends PromiseLike<A>, // the type of Promise specified in `options`
    C extends PromisePool.PromiseClass<A, P2> // a helper class
    > {
    constructor(
        source: IterableIterator<P> | P | (() => (P | undefined)) | A,
        concurrency: number,
        options?: PromisePool.Options<A, P2, C>
    );

    promise(): P2;
    start(): P2;
}

declare namespace PromisePool {
    interface PromiseClass<A, P extends PromiseLike<A>> {
        new(callback: (resolve: (value?: A | P) => void, reject: (reason?: any) => void) => void): P;
    }

    interface Options<A, P extends PromiseLike<A>, C extends PromiseClass<A, P>> {
        promise?: C;
    }
}

How can I specify the Options type so that:

  • Any newable promise library (as above) can be specified, with the types automatically inferred
  • It falls back to Promise if not specified
  • I can specify the promise type as the return value of PromisePool.promise and PromisePool.start
1

1 Answers

0
votes

I was misusing my own types because I thought Bluebird.resolve(3) would function in the same way as Bluebird, even though I landed myself in this mess with the intention of distinguishing between the two. At any rate, this works:

import Bluebird = require("bluebird");
import ES6Promise = require("es6-promise");

interface PromiseClass<A, P extends PromiseLike<A>> {
    new(callback: (resolve: (value?: A | P) => void, reject: (reason?: any) => void) => void): P;
}

interface Options<A, P extends PromiseLike<A>, C extends PromiseClass<A, P>> {
    promise?: C;
}

function foo<A, P extends PromiseLike<A>, C extends PromiseClass<A, P>>(options: Options<A, P, C>, val: A): P {
    return new options.promise((resolve, reject) => resolve(val));
}

const baz: Bluebird<number> = foo({ promise: Bluebird }, 5);
const quux: ES6Promise.Promise<number> = foo({ promise: ES6Promise.Promise }, 5);

I just needed to pass the class as the value of promise, not an instance.