import "dart:async";
import "package:riverpod/riverpod.dart";
Future<void> asyncTask1() async => print("async task 1");
Future<void> asyncTask2() async => throw Exception("exception sample");
Future<void> main() async {
try {
await (asyncTask1(), asyncTask2()).wait;
} on ParallelWaitError catch (e) {
final (error1, error2) = e.errors;
print(error2.runtimeType.toString());
if (error2 is AsyncError) {
print(error2.error);
}
}
}
このコードを実行してみましょう。
async task 1
AsyncError
と表示されるかと思います。error2 is AsyncError
が通っていないのに、error2.runtimeType
はAsyncError
?????
ParallelWaitErrorの癖
dart:async
における、(a, b).wait
というメソッドは便利ですが、エラーハンドリングに癖があります。
Future<void> main() async {
try {
await (asyncTask1(), asyncTask2()).wait;
} on ParallelWaitError catch (e) {
final (error1, error2) = e.errors;
print(e.errors.runtimeType.toString());
print(e.values.runtimeType.toString());
}
}
この結果からわかるように、ParallelWaitError
はジェネリクスを解決すると、ParallelWaitError<(Null, Null), (AsyncError?, AsyncError?)>
という型であるとわかります。
つまり、エラーの値を取得するには、
import "dart:async";
Future<void> asyncTask1() async => print("async task 1");
Future<void> asyncTask2() async => throw Exception("exception sample");
Future<void> main() async {
try {
await (asyncTask1(), asyncTask2()).wait;
} on ParallelWaitError<dynamic, (AsyncError?, AsyncError?)> catch (e) {
final (error1,error2) = e.errors;
print(error1?.error);
print(error2?.error);
}
}
このように、AsyncError
からさらに値を取得する必要があります。これであれば、
async task 1
null
Exception: exception sample
と出力されるので、期待通りエラーの結果が取得できました。
あらためて最初のコードに振り返りましょう。
import "dart:async";
import "package:riverpod/riverpod.dart";
Future<void> asyncTask1() async => print("async task 1");
Future<void> asyncTask2() async => throw Exception("exception sample");
Future<void> main() async {
try {
await (asyncTask1(), asyncTask2()).wait;
} on ParallelWaitError catch (e) {
final (error1, error2) = e.errors;
print(error2.runtimeType.toString());
if (error2 is AsyncError) {
print(error2.error);
}
}
}
うーん。動きそうなものですが。
実は…
実際のところ、簡潔に説明するためのコードなので最初の時点で気づく人は気付いていると思いますが…
import "dart:async";
import "package:riverpod/riverpod.dart";
Future<void> asyncTask1() async => print("async task 1");
Future<void> asyncTask2() async => throw Exception("exception sample");
Future<void> main() async {
try {
await (asyncTask1(), asyncTask2()).wait;
} on ParallelWaitError catch (error, stackTrace) {
final (error1, error2) = error.errors;
print(error2.runtimeType);
print(error2.runtimeType.hashCode);
final asyncError = AsyncError(error, stackTrace);
print(asyncError.runtimeType);
print(asyncError.runtimeType.hashCode);
}
}
このコードを実行してみましょう。
async task 1
AsyncError
478785560
AsyncError<dynamic>
129899266
1つめのAsyncErrorと2つめのAsyncErrorは異なり、後者にはAsyncError<dynamic>
になっています。なぜ?
そう、これが落とし穴なのですが、
import "package:riverpod/riverpod.dart";
riverpodをインポートしています。そしてなんと、PararellWaitErrorが解決するAsyncErrorは、
AsyncError implements Error(dart:async)
なのですが、上記のコードでは
AsyncError<T> extends AsyncValue<T> (riverpod)
のAsyncError
が優先されます。そんなあ。というか何事もなくビルド通って実行時エラーになったりすらしないんです。
というわけで上記のコードの本来の正解は、dart:async
を使うために明示的に名前を設定して、
import "dart:async" as dart_async;
import "package:riverpod/riverpod.dart";
Future<void> asyncTask1() async => print("async task 1");
Future<void> asyncTask2() async => throw Exception("exception sample");
Future<void> main() async {
try {
await (asyncTask1(), asyncTask2()).wait;
} on ParallelWaitError catch (e) {
final (error1, error2) = e.errors;
print(error2.runtimeType.toString());
if (error2 is dart_async.AsyncError) {
print(error2.error);
}
}
}
dart_async.AsyncError
を明示することで、
async task 1
AsyncError
Exception: exception sample
期待通りの出力を得ることができました。riverpodはここでは使っていないので、Lintが未使用のインポートを警告するようにもなりました。 これに3時間くらい費やしました
余談
これってパッケージシステムのセキュリティ的にどうなんでしょうね。悪意のあるパッケージがdart:async
とかと同じ名前のクラスとかを定義できてしまったりしないんでしょうか…?