TypeScriptでは、非同期関数(async/await)は常にPromiseを返します。戻り値の型は次のように記述します:Promise<Type>。関数を記述する際には、TypeScriptがPromise内の型について正しく動作するように、結果を明示的に型付けすることが重要です。中間値が宣言された値と一致しない場合、型のエラーが発生することがあります。
非同期関数:
async function fetchUser(id: number): Promise<User> { const response = await fetch(`/api/user/${id}`); const data: User = await response.json(); return data; }
関数がエラーや拒否を返す必要がある場合、Promiseの型はエラーを直接記述すべきではありません(Promise<Error>)。なぜなら、Promiseの拒否は型付けされず、catchを通じてキャッチされるからです。
Promiseチェーンの型付けも重要です:
function getNumber(): Promise<number> { return Promise.resolve(42); } getNumber().then(val => val.toFixed(2)); // TypeScriptはvalが数値であることを知っています
質問: 非同期関数はPromise以外の何かを返すことができますか?
回答: いいえ。非同期関数は常にPromiseオブジェクトを返します。明示的に非Promiseを返そうとすると、TypeScriptは自動的に戻り値をPromiseでラップします。
async function test() { return 1; } const result = test(); type ResultType = typeof result; // Promise<number>
物語
ある開発者が戻り値の型付けをせずに関数を書き、特定の型のオブジェクトを返すと考えていました。ロジックが変更された際に関数が異なる型を返すようになり、TypeScriptが型の推論に基づいてエラーを出さなかったため、これにすぐには気づきませんでした。最終的には、呼び出し元が存在しないプロパティにアクセスしようとしたときにのみ、ランタイムでエラーが発生しました。
物語
あるプロジェクトでは、Promiseのチェーンが型付けされていませんでした。結果に対して存在しないメソッドを呼び出そうとすると、エラーが発生しました。例えば、then(response => response.data)の結果はanyと見なされ、フィールドやメソッドのエラーがランタイムでのみ発生しました。このエラーは本番環境に持ち込まれ、ユーザーを介してのみ発見されました。
物語
非同期関数は型Promise<Error>で宣言され、エラーをキャッチできると思われていましたが、実際にはエラーはthrowを通じて投げられず、Promiseは異なる型の値で拒否されていました。これにより、エラーと有効な値の暗黙の混合が発生し、Promiseの結果処理のロジックにバグが生じました。