Yaroslav Admin
Yaroslav Admin

Reputation: 14535

Augmentation of Promise breaks type inference for new Promise(...)

I'm trying to augment Promise by adding extra method to it as described in the TypeScript documentation - Global augmentation.

And it works well in the test 1. But for some reason it breaks type inference for the Promise created using constructor in the test 2. TypeScript stops inferring type as Promise<boolean> and infers it as Promise<{}> instead. If I remove my augmentation, generic type for Promise created using constructor is inferred correctly.

Any ideas why it happens and how to fix it?

Here is a simplified example of what I'm trying to do:

// src/test.ts

// augmentation
interface Promise<T> {
    myCustomMethod(): T;
}

// test 1
let data: number = Promise.resolve(42)
    .then((value) => value + 4)
    .myCustomMethod();

// test 2
let p2: Promise<boolean> = new Promise((resolve) => {
    resolve(true);
})

And my config:

// tsconfig.json
{
    "compilerOptions": {
        "module": "es6",
        "target": "es6",
        "baseUrl": "./src"
    }
}

Error I'm getting is:

src/test.ts(9,5): error TS2322: Type 'Promise<{}>' is not assignable to type 'Promise<boolean>'.
  Type '{}' is not assignable to type 'boolean'.

UPD

I see that type is always inferred as Promise<{}> for the test 2. But now I can't understand, why this error is not reported without my augmentation. Since Type '{}' is not assignable to type 'boolean'., shouldn't this error appear even without augmentation?

Just trying to understand if it's intended or should I report it to TypeScript developers.

Upvotes: 2

Views: 281

Answers (2)

artem
artem

Reputation: 51629

TypeScript always infers Promise<{}> type for this construct, even without your augmentation:

new Promise((resolve, reject) => {
    resolve(true);
})

as evidenced by error message for this assignment:

let z: void = new Promise((resolve, reject) => {
    resolve(true);
})

Type 'Promise<{}>' is not assignable to type 'void'

So, when you add myCustomMethod(): T; to Promise, as a side effect it makes Promise<{}> incompatible with Promise<boolean>.

Either don't do that (when you call myCustomMethod() on unresolved promise, where will it get the T value it should return?) , or use explicit generic argument when constructing your promise:

let p2: Promise<boolean> = new Promise<boolean>((resolve) => {
    resolve(true);
})

Upvotes: 1

puzzle
puzzle

Reputation: 6131

The compiler can infer the type of a generic from the argument in simple cases like calling Promise.resolve() with a constant in Promise.resolve(42). It may fail to do so in more complex situations, like when the type only becomes apparent in your call to resolve(true) .

This is documented in the TypeScript Handbook:

While type argument inference can be a helpful tool to keep code shorter and more readable, you may need to explicitly pass in the type arguments as we did in the previous example when the compiler fails to infer the type, as may happen in more complex examples.

You can work around the issue by using

let p2: Promise<boolean> = new Promise<boolean>(resolve => {
   resolve(true);
});

Upvotes: 2

Related Questions