ウェブサイト検索

typescript - Promiseの拡張は新しいPromise(...)の型推論を壊す


TypeScriptのドキュメント - グローバル拡張に記載されているように、Promise に追加のメソッドを追加して拡張しようとしています。

そして、テスト1ではうまく動作します。しかし、なぜかテスト2でコンストラクタを使用して作成されたPromiseの型推論が壊れます。TypeScriptは型をPromise<boolean>として推論するのをやめ、代わりにPromise<{}>として推論します。もし私の拡張を削除すると、コンストラクタを使用して作成されたPromiseのジェネリック型は正しく推論されます。

なぜそれが起こるのか、そしてどうやって直すのか、何かアイデアはありますか?

これは私がやろうとしていることの簡単な例です:

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

そして私の設定:

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

私が受け取っているエラーは次の通りです:

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

更新

テスト2ではその型が常にPromise<{}>として推論されることがわかります。しかし、なぜ私の拡張なしでこのエラーが報告されないのか、今は理解できません。Type ' {} ' is not assignable to type ' boolean 'なので、このエラーは拡張なしでも表示されるべきではないでしょうか?

これは意図されたものかどうか理解しようとしているだけです。それともTypeScriptの開発者に報告すべきでしょうか。

回答 #1

コンパイラは、Promise.resolve(42) のように定数を使って Promise.resolve() を呼び出す簡単な場合には、引数からジェネリックの型を推論することができます。しかし、型が resolve(true) の呼び出し時にのみ明らかになるようなより複雑な状況では、推論に失敗することがあります。

これはTypeScriptハンドブックに文書化されています:

型引数の推論はコードを短く、より読みやすく保つための便利なツールになり得ますが、コンパイラが型を推論できない場合には、前の例で行ったように型引数を明示的に渡す必要があることがあります。これはより複雑な例で起こり得ます。

次の方法を使って問題を回避できます

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

回答 #2

TypeScript は、この構造に対して、あなたの拡張がなくても常に Promise<{}> 型を推論します:

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

この課題のエラーメッセージによって証明されるように:

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

型 'Promise<{}>' は型 'void' に代入できません

ですので、Promise に myCustomMethod(): T; を追加すると、副作用として Promise<{}> が Promise<boolean> と互換性がなくなります。

未解決の Promise に対して myCustomMethod() を呼び出す場合は、それが返すべき T の値をどこで取得するのか分からないので、やらないか、あるいは Promise を構築するときに明示的なジェネリック引数を使用してください。

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