The Awaited Type and Promise Improvements
TL;DR
Promiseをアンラップするビルトインユーティリティタイプが追加された。
Promiseの雑な説明
JavaScriptの fetch
を例に説明する。この関数はAPIリクエストを実行する。
fetch("https://my-domain/users/102");
リクエストが完了するのは、1秒後かもしれないし10秒後かもしれない。このような非同期処理を扱うため fetch()
の戻り値は Promise
というオブジェクトになっている。
fetch("https://...");
// Promise {
// then: () => {}
// catch: () => {}
// }
fetch("https://...")
// リクエストが正常終了した時の処理
.then(() => {
console.log("レロレロ レロレロ レロレロレロ");
})
// リクエストが失敗した時の処理
.catch(() => {
console.log("『てめーはおれを怒らせた』");
})
;
TypeScriptは Promise
オブジェクトの型情報を持っている。
async function fetchUser() {
return fetch("https://...");
}
// 戻り値がPromiseだと推論される
// Promiseはthenメソッドを持っている
// thenには関数を渡すことができる
fetchUser().then(() => { ... });
型情報のおかげで、タイポも見つけやすくなるし、値を推論することもできる。
fetchUser().theeeeen(); // Error
// 推論によって「id」がエディタでサジェストされる
fetchUser().then(user => {
console.log(user.id);
});
では、fetchUser
の返す値を「型」として定義したい時はどうするか。
愚直にやるならレスポンスをインターフェースにすれば良い。
async function fetchUser() {
return { id: 1 };
}
type FetchUserType = Promise<{ id: number }>;
ただしこの方法は、エンドポイントごとに同じような型を定義するわずらわしさがある。
type FetchUserType = Promise<{ id: number }>;
type FetchUserProfileType = Promise<{ id: number; age: number; }>;
type FetchUserHistoryType = Promise<{ id: number; lastLoggedIn: date; }>;
...
さらにリクエストを扱う関数など、型の扱いがやっかいになる。
// 全部型を列挙するか?
async function fn(fetch: () =>
FetchUserType | FetchUserProfileType | FetchUserHistoryType
) {
return {
data: await fetch(),
requestedAt: new Date(),
};
}
// anyで許容するか?
async function fn2(fetch: () => Promise<any>) {
return {
data: await fetch(),
requestedAt: new Date(),
};
}
Promiseから値の型を導出(従来)
Promise
の値を取り出すときは infer
で推論するのが通例だった。
async function fetchUser() {
return { id: 1 };
}
// (1)fetchUser関数の戻り値を型定義する
type FetchUserReturnType = ReturnType<typeof fetchUser>;
// (2)Promise<R>からRを取り出す
type UnWrapped<T> = T extends Promise<infer R> ? R : T;
// (3)Promise<fetchUserの戻り値>から戻り値を取り出す
type XXX = UnWrapped<FetchUserReturnType>;
// { id: number }
Pormiseから値の型を導出(新)
Awaited というビルトインの型が追加された。このビルトイン型が今回追加された機能。
infer
を使った導出は Awaited
に置き換えることができる。
async function fetchUser() {
return { id: 1 };
}
// (1)fetchUser関数の戻り値を型定義する
type FetchUserReturnType = ReturnType<typeof fetchUser>;
// (2)Promise<fetchUserの戻り値>から戻り値を取り出す
type XXX = Awaited<FetchUserReturnType>;
// { id: number }
Awaitedの優れた点:ネストに対応
Promiseのネストを表現してみる。
function promisefy<T>(value: T): Promise<T> {
return new Promise(resolve => resolve(value));
}
const promise = promisefy(promisefy(promisefy(100)));
// promise:Promise<Promise<Promise<number>>>
Promiseの内包するnumberを取り出すにはどうしたらいいだろうか?
前述の infer
を使った導出はネストに対応していない。
type UnWrapped<T> = T extends Promise<infer R> ? R : T;
type XXX = UnWrapped<typeof promise>;
// Promise<Promise<number>
// まだネストしてる
type YYY = UnWrapped<UnWrapped<UnWrapped<typeof promise>>>;
// number
// ネストの数に依存している
がんばれば値を導出できるが「開発者が都度がんばる」というところに問題がある。
type UnWrapped<T> = T extends Promise<infer R>
? R extends Promise<any> ? UnWrapped<R> : R
: T;
type XXX = UnWrapped<typeof promise>;
// number
// 導出できた....けど...UnWrappedの型、読める??
Awaited
を使うと簡単に値を導出できる。
type XXX = Awaited<typeof promise>;
// number
Last updated