Dartの非同期関数内におけるtry-catchでExceptionを捕捉できないときの対処法
はじめに
Flutterのある関数内でExceptionをcatchしてアプリケーション用のエラーにマッピングしようとしたところ、Exceptionを捕捉することができなかった。しかしその関数の外ではExceptionを捕捉できた。
この問題にはtry-catchを非同期関数内で使う際の注意点があったので残しておく。
問題のコード
以下のような、fetchFromAPI
でAPIから取得したデータをそのままreturnするsomeAsyncService
のような関数があるとする。このとき、fetchFromAPI
で投げられたExceptionをsomeAsyncService
で捕捉するためにtry-catchを用意する。
Future<String> fetchFromAPI() async {
await Future.delayed(Duration(seconds: 1));
throw Exception('error api');
}
Future<String> someAsyncService() {
try {
return fetchFromAPI();
} catch (exception) {
print('someAsyncService: $exception');
rethrow;
}
}
void main() async {
try {
final result = await someAsyncService();
print(result);
} catch (exception) {
print('main: $exception');
rethrow;
}
}
しかし、実行してみるとsomeAsyncService
でExceptionを捕捉できたことを示す出力結果は得られない。
main: Exception: error api
対処法
この問題の対処法としては、someAsyncService
内のfetchFromAPI()
をawait
する必要がある。つまり、以下のようなコードであればsomeAsyncService
内でExceptionを捕捉することができる。
Future<String> someAsyncService() async {
try {
final result = await fetchFromAPI();
return result;
// もしくは、return await fetchFromAPI(); としても良い。
} catch (exception) {
print('someAsyncService: $exception');
rethrow;
}
}
これで期待する出力結果を得ることができた。
someAsyncFunction: Exception: error
main: Exception: error
問題の原因
この問題の原因としては、非同期関数であるfetchFromAPI
の完了をawait
で待たれていないため、Exceptionを投げる頃にはsomeAsyncService
が終了しておりExceptionを捕捉できないというわけだ。
非同期処理のコードを書く上での注意点はドキュメントにも書かれている。要するに、Future
(非同期関数)から成功時やエラー時も含めた結果を取得したければそれを待つ必要があるということだ。
In general, when writing asynchronous code, you should always await a future when it is produced, and not wait until after another asynchronous delay. That ensures that you are ready to receive any error that the future might produce, which is important because an asynchronous error that no-one is awaiting is an uncaught error and may terminate the running program.
おわり
基本的に非同期処理はawait
をつける、という方針でやっていれば問題ないようなに思う。しかし、async
のついていない関数内ではawait
をつけなくてもlintエラーが出ないのでたまに忘れてしまいそう。他の対処法としてはカスタムリントを作成するか、テストをちゃんと書くかだろうか。