【问题标题】:How can I make TypeScript infer the correct types for these parameters?如何让 TypeScript 为这些参数推断正确的类型?
【发布时间】:2021-03-10 13:36:05
【问题描述】:

我正在尝试使一个库在 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>> {
    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.)

如果我指定 pool 的完整类型,它会起作用:

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

我如何告诉 TypeScript pool.start 将返回 PromiseLike&lt;number&gt; 而无需写出来?

【问题讨论】:

  • 你想从中推断出数字吗:new FakePromise((_resolve, _reject) => true), 1); ?
  • 是的,或多或少。

标签: typescript


【解决方案1】:

只需从这个类中去掉第二个和第三个泛型参数 (P, P2):

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

    start(): PromiseLike<A>;
}

完整示例:

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

更新 这个解决方案怎么样:

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);

【讨论】:

  • 这并没有完成同样的事情(接受任何类型的承诺并吐出选项中指定类型的承诺),因为类型只是扁平化为PromiseLike .
  • 不幸的是,这仍然会使类型变平。这背后的想法是在类型本身中建立,例如,如果您将 Bluebird 作为选项中的承诺库传递,那么它会返回 Bluebird 承诺,而此解决方案将丢失该信息。然而,你的回答很有用,因为它告诉我我不需要担心传递给start 的承诺类型,所以谢谢你。我也许可以对此进行调整。
猜你喜欢
  • 1970-01-01
  • 2011-02-20
  • 1970-01-01
  • 2018-08-07
  • 2016-12-05
  • 2021-04-09
  • 2021-04-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多