简体   繁体   中英

How can I make TypeScript infer the correct types for these parameters?

I'm trying to make a library generic across promise libraries, but I've come across a case where, within the same type, a single type parameter is inferred to be two different types:

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>> {
    promise?: PromiseClass<A, P>;
}

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`
    > {
    constructor(
        source: P,
        concurrency: number,
        options?: Options<A, P2>
    );

    start(): P2;
}

class FakePromise<T> implements PromiseLike<T> {
    constructor(
        _callback: (
            resolve: (value?: T | FakePromise<T>) => void,
            reject: (reason?: any) => void) => void) {
    }

    then<TResult1 = T, TResult2 = never>(
        _onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
        _onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null):
        PromiseLike<TResult1 | TResult2> {
        throw new Error("stub");
    }
};


// erroneously inferred as PromisePool<unknown, FakePromise<number>,
// PromiseLike<unknown>> instead of PromisePool<number, FakePromise<number>,
// PromiseLike<number>> i.e. A is inferred as both `unknown` and `number`
const pool = new PromisePool(
    new FakePromise<number>((_resolve, _reject) => true), 1);

// error:
// Type 'PromiseLike<unknown>' is not assignable to type 'PromiseLike<number>'.
//   Type 'unknown' is not assignable to type 'number'.
const promise: PromiseLike<number> = pool.start();

( Playground link. )

It works if I specify the full type of pool :

const pool: PromisePool<number, FakePromise<number>, PromiseLike<number>> = new PromisePool(
    new FakePromise<number>((_resolve, _reject) => true), 1);

How can I tell TypeScript that pool.start will return a PromiseLike<number> without having to write it out?

Just get rid of second and third generic arguments (P, P2) from this class:

declare class PromisePool<
    A,
    > {
    constructor(
        source: PromiseLike<A>,
        concurrency: number,
        options?: Options<A,  PromiseLike<A>>
    );

    start(): PromiseLike<A>;
}

Full example:

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>> {
    promise?: PromiseClass<A, P>;
}

declare class PromisePool<
    A,
    > {
    constructor(
        source: PromiseLike<A>,
        concurrency: number,
        options?: Options<A,  PromiseLike<A>>
    );

    start(): PromiseLike<A>;
}

class FakePromise<T> implements PromiseLike<T> {
    constructor(
        _callback: (
            resolve: (value?: T | FakePromise<T>) => void,
            reject: (reason?: any) => void) => void) {
    }

    then<TResult1 = T, TResult2 = never>(
        _onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
        _onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null):
        PromiseLike<TResult1 | TResult2> {
        throw new Error("stub");
    }
};


// erroneously inferred as PromisePool<unknown, FakePromise<number>,
// PromiseLike<unknown>> instead of PromisePool<number, FakePromise<number>,
// PromiseLike<number>> i.e. A is inferred as both `unknown` and `number`
const pool = new PromisePool(
    new FakePromise<number>((_resolve, _reject) => true), 1);


const promise = pool.start(); // ok

Playground

UPDATE What about this solution:

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>> {
    promise?: PromiseClass<A, P>;
}

declare class PromisePool<
    A,
    T,
    > {
    constructor(
        source: PromiseLike<A>,
        concurrency: T,
        options?: Options<A,  PromiseLike<A>>
    );

    start(): PromiseLike<T>;
}

class FakePromise<T> implements PromiseLike<T> {
    constructor(
        _callback: (
            resolve: (value?: T | FakePromise<T>) => void,
            reject: (reason?: any) => void) => void) {
    }

    then<TResult1 = T, TResult2 = never>(
        _onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
        _onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null):
        PromiseLike<TResult1 | TResult2> {
        throw new Error("stub");
    }
};


// erroneously inferred as PromisePool<unknown, FakePromise<number>,
// PromiseLike<unknown>> instead of PromisePool<number, FakePromise<number>,
// PromiseLike<number>> i.e. A is inferred as both `unknown` and `number`
const fake = new FakePromise<number>((_resolve, _reject) => true);
const pool = new PromisePool(fake, 1);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM