2
1

このAsyncError、AsyncErrorだけどAsyncErrorじゃない

Posted at
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.runtimeTypeAsyncError?????

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とかと同じ名前のクラスとかを定義できてしまったりしないんでしょうか…?

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1