課題

Promise などのラップされた型があるとき、その中にある型を取得するにはどうすればよ いでしょうか? たとえば Promise<ExampleType> から ExampleType を取得するには どうすればよいでしょうか?

解答

TypeScript のそれほど知られていない機能を理解していることが要求される、興味深い 課題です。

しかし、そのことを説明する前に、まず課題を分析しましょう。この課題の作者は、型を アンラップすることを求めています。アンラップとは何でしょうか? アンラップとは、あ る型から内部の型を抽出することです。

例により説明します。Promise<string> という型があるとき、Promise 型をアンラッ プすると、string 型を得ます。外側の型から内側の型を取得したのです。

ここで、型を再帰的にアンラップする必要があることに注意してください。たとえ ば、Promise<Promise<string>> という型に対しては、string 型を返す必要がありま す。

それでは課題に入りましょう。まずは最も単純なケースから始めます。Awaited という 型を、Promise<string> を受け取ったとき string を返すように定義します。その他 の場合については、Promise ではないため T 自体を返すようにします:

type Awaited<T> = T extends Promise<string> ? string : T;

しかし、このアプローチには問題があります。この方法により対応できるのは文字列の Promise のみですが、実際は任意のケースについて対応できるようにする必要があるの です。そのためにはどうすればよいでしょうか? Promise に含まれる型が何であるかわ からない場合、そこからどのように型を取得すればよいでしょうか?

こうした目的のために、TypeScript には Conditional 型の型推論があります! コンパイ ラに対し、「型の種類がわかったら、それを型変数に割り当ててください」と伝えること ができるのです。詳しくは Conditional 型の型推論についてを ご覧ください。

型推論を用いて、上の解答を書き換えましょう。Conditional 型において Promise<string> についてチェックするのではなく、stringinfer R へと置き 換えます。そこに何が入るかわからないからです。わかっていることは、ある型を内部に もつ Promise<T> であるということだけです。

TypeScript が Promise の内部の型を把握すると、その型は型変数 R に割り当てら れ、true ブランチにおいて利用可能となります。そこで R を返却します:

type Awaited<T> = T extends Promise<infer R> ? R : T;

ほぼ完成ですが、このままでは Promise<Promise<string>> から Promise<string> が得られてしまいます。これを防ぐため、同じ処理を再帰的に繰り返す必要があります。 これは Awaited 型自体を用いることで実現可能です:

type Awaited<T> = T extends Promise<infer R> ? Awaited<R> : T;

参考