Dart の非同期メソッドからスローされた例外の処理方法

# はじめに

Dart では例外をどのようにハンドリングしたらいいの？ `return Future.error(～)` ってなに？ という疑問が湧いたので色々と試してみました。

# 分かったこと

1. Dart では `Exception` だけでなく、`Error` や任意のオブジェクト (`String` 等) をスローできる
1. 任意のオブジェクト (例えば `String`) は `} on String catch (e) {` として明示的にキャッチできる
1. `Error` は `} on Error catch (e) {` としてもキャッチできず、`} catch (e) {` に流れる
1. 非同期メソッド内で `return Future.error([任意のオブジェクト]);` とした場合は呼び元に任意のオブジェクトがスローされる
1. `try` 句で「`await` なし」でコールした非同期メソッドからスローされた例外は `Uncaught Error` として扱われる
1. `then` 句で「`await` なし」でコールした非同期メソッドからスローされた例外は `catchError` 句で処理できる

# 検証

## 例外をスローする非同期メソッド

```dart
// [A] Exception をスロー
Future _execA() async {
  throw Exception('Hello Exception !!');
}

// [B] Error をスロー
Future _execB() async {
  throw Error;
}

// [C] String をスロー
Future _execC() async {
  throw 'Hello String !!';
}

// [D] Future.error(String) を返す
Future _execD() async {
  return Future.error('Hello Future.error(String) !!');
}

// [E] Future.error(Exception) を返す
Future _execE() async {
  return Future.error(Exception('Hello Future.error(Exception) !!'));
}
```

## 01-05. try 内での例外スロー [await あり]

`catch` では、`Exception`、`Error`、`String` を明示的にキャッチします。

```dart
// 01: Exception をスロー
try {
  await _execA();
} on Exception catch (e) {
  print('01a: $e'); // ★ ここに入る
} on Error catch (e) {
  print('01b: $e');
} on String catch (e) {
  print('01c: $e');
} catch (e) {
  print('01d: $e');
}

// 02: Error をスロー
try {
  await _execB();
} on Exception catch (e) {
  print('02a: $e');
} on Error catch (e) {
  print('02b: $e'); // ここには入らない !?
} on String catch (e) {
  print('02c: $e');
} catch (e) {
  print('02d: $e'); // ★ ここに入る
}

// 03: String をスロー
try {
  await _execC();
} on Exception catch (e) {
  print('03a: $e');
} on Error catch (e) {
  print('03b: $e');
} on String catch (e) {
  print('03c: $e'); // ★ ここに入る
} catch (e) {
  print('03d: $e');
}

// 04: Future.error(String) を返す
try {
  await _execD();
} on Exception catch (e) {
  print('04a: $e');
} on Error catch (e) {
  print('04b: $e');
} on String catch (e) {
  print('04c: $e'); // ★ ここに入る
} catch (e) {
  print('04d: $e');
}

// 05: Future.error(Exception) を返す
try {
  await _execE();
} on Exception catch (e) {
  print('05a: $e'); // ★ ここに入る
} on Error catch (e) {
  print('05b: $e');
} on String catch (e) {
  print('05c: $e');
} catch (e) {
  print('05d: $e');
}
```

この結果はこんな感じ、、

```
01a: Exception: Hello Exception !!
02d: Error
03c: Hello String !!
04c: Hello Future.error(String) !!
05a: Exception: Hello Future.error(Exception) !!
```

`Error` がスローされても `} on Error catch (e) {` には入らないんですね !?
`} catch (e) {` に流れてきちゃいました。これは気を付けないと。
`return Future.error( ... );` は引数で渡したオブジェクトが呼び元にスローされるんですね。

## 06-10. try 内での例外スロー [await なし]

次は、`try` 句で非同期メソッドを「`await` なし」で呼び出します。

```dart
// 6: Exception をスロー [await なし]
try {
  _execA();
} on Exception catch (e) {
  print('06a: $e');
} on Error catch (e) {
  print('06b: $e');
} on String catch (e) {
  print('06c: $e');
} catch (e) {
  print('06d: $e');
}
// ★ Uncaught Error

// 7: Error をスロー [await なし]
try {
  _execB();
} on Exception catch (e) {
  print('07a: $e');
} on Error catch (e) {
  print('07b: $e');
} on String catch (e) {
  print('07c: $e');
} catch (e) {
  print('07d: $e');
}
// ★ Uncaught Error

// 8: String をスロー [await なし]
try {
  _execC();
} on Exception catch (e) {
  print('08a: $e');
} on Error catch (e) {
  print('08b: $e');
} on String catch (e) {
  print('08c: $e');
} catch (e) {
  print('08d: $e');
}
// ★ Uncaught Error

// 9: Future.error(String) を返す [await なし]
try {
  _execD();
} on Exception catch (e) {
  print('09a: $e');
} on Error catch (e) {
  print('09b: $e');
} on String catch (e) {
  print('09c: $e');
} catch (e) {
  print('09d: $e');
}
// ★ Uncaught Error

// 10: Future.error(Exception) を返す [await なし]
try {
  _execE();
} on Exception catch (e) {
  print('10a: $e');
} on Error catch (e) {
  print('10b: $e');
} on String catch (e) {
  print('10c: $e');
} catch (e) {
  print('10d: $e');
}
// ★ Uncaught Error
```

この結果はこんな感じ、、

```
Uncaught Error: Exception: Hello Exception !!
Uncaught Error: Error
Uncaught Error: Hello String !!
Uncaught Error: Hello Future.error(String) !!
Uncaught Error: Exception: Hello Future.error(Exception) !!
```

全て `Uncaught Error` として扱われるんですね。

## 11-15. then - catchError

次は非同期メソッドの戻り値の `Future` を `then - catchError` で処理します。

```dart
// 11: Exception をスロー
_execA()
    .then((n) => print('11:'),
        onError: (e) => print('11e: $e')) // ★ ここに入る
    .catchError((e) => print('11f: $e'));

// 12: Error をスロー
_execB()
    .then((n) => print('12:'),
        onError: (e) => print('12e: $e')) // ★ ここに入る
    .catchError((e) => print('12f: $e'));

// 13: String をスロー
_execC()
    .then((n) => print('13:'),
        onError: (e) => print('13e: $e')) // ★ ここに入る
    .catchError((e) => print('13f: $e'));

// 14: Future.error(String) を返す
_execD()
    .then((n) => print('14:'),
        onError: (e) => print('14e: $e')) // ★ ここに入る
    .catchError((e) => print('14f: $e'));

// 15: Future.error(Exception) を返す
_execE()
    .then((n) => print('15:'),
        onError: (e) => print('15e: $e')) // ★ ここに入る
    .catchError((e) => print('15f: $e'));
```

この結果はこんな感じ、、

```
11e: Exception: Hello Exception !!
12e: Error
13e: Hello String !!
14e: Hello Future.error(String) !!
15e: Exception: Hello Future.error(Exception) !!
```

非同期メソッドがすべて例外をスローするので、`then` には入らず、すべて `onError` に入ります。

## 16-20. then 内での例外スロー [await あり]

次は `then` 句内で非同期メソッドを呼びます。

```dart
// 16: Exception をスロー
Future.delayed(Duration.zero)
    .then((n) async => await _execA(),
        onError: (e) => print('16e: $e'))
    .catchError((e) => print('16f: $e')); // ★ ここに入る

// 17: Error をスロー
Future.delayed(Duration.zero)
    .then((n) async => await _execB(),
        onError: (e) => print('17e: $e'))
    .catchError((e) => print('17f: $e')); // ★ ここに入る

// 18: String をスロー
Future.delayed(Duration.zero)
    .then((n) async => await _execC(),
        onError: (e) => print('18e: $e'))
    .catchError((e) => print('18f: $e')); // ★ ここに入る

// 19: Future.error(String) を返す
Future.delayed(Duration.zero)
    .then((n) async => await _execD(),
        onError: (e) => print('19e: $e'))
    .catchError((e) => print('19f: $e')); // ★ ここに入る

// 20: Future.error(Exception) を返す
Future.delayed(Duration.zero)
    .then((n) async => await _execE(),
        onError: (e) => print('20e: $e'))
    .catchError((e) => print('20f: $e')); // ★ ここに入る
```

この結果はこんな感じ、、

```
16f: Exception: Hello Exception !!
17f: Error
18f: Hello String !!
19f: Hello Future.error(String) !!
20f: Exception: Hello Future.error(Exception) !!
```

今度は `onError` ではなく `catchError` に入ります。

## 21-25. then 内での例外スロー [await なし]

最後も `then` 句内で非同期メソッドを呼びますが、今度は `await` を付けません。
このような呼び出しからスローされる例外は `try - catch` の際は `Uncaught Error` として扱われましたが、今度は、、

```dart
// 21: Exception をスロー [await なし]
Future.delayed(Duration.zero)
    .then((n) => _execA(),
        onError: (e) => print('21e: $e'))
    .catchError((e) => print('21f: $e')); // ★ ここに入る

// 22: Error をスロー [await なし]
Future.delayed(Duration.zero)
    .then((n) => _execB(),
        onError: (e) => print('22e: $e'))
    .catchError((e) => print('22f: $e')); // ★ ここに入る

// 23: String をスロー [await なし]
Future.delayed(Duration.zero)
    .then((n) => _execC(),
        onError: (e) => print('23e: $e'))
    .catchError((e) => print('23f: $e')); // ★ ここに入る

// 24: Future.error(String) を返す [await なし]
Future.delayed(Duration.zero)
    .then((n) => _execD(),
        onError: (e) => print('24e: $e'))
    .catchError((e) => print('24f: $e')); // ★ ここに入る

// 25: Future.error(Exception) を返す [await なし]
Future.delayed(Duration.zero)
    .then((n) => _execE(),
        onError: (e) => print('25e: $e'))
    .catchError((e) => print('25f: $e')); // ★ ここに入る
```

```
21f: Exception: Hello Exception !!
22f: Error
23f: Hello String !!
24f: Hello Future.error(String) !!
25f: Exception: Hello Future.error(Exception) !!
```

こんな感じで `catchError` に入ってくるんですね。

# おわりに

`on Error` では `throw Error()` を処理できないんですね、、

|                                  | on Exception | on Error | on String |
| -------------------------------- | ------------ | -------- | --------- |
| throw Exception()              | ✓            |          |           |
| throw Error()                  |              |          |           |
| throw ''                       |              |          | ✓         |
| return Future.error(String)    |              |          | ✓         |
| return Future.error(Exception) | ✓            |          |           |

「`await` なし」の非同期メソッド呼び出し時の例外を `catchError` で処理できるのは知りませんでした。

|             | try-catch        | then-catchError |
| ----------- | ---------------- | --------------- |
| await あり | ✓                | ✓               |
| await なし | Uncaught Error | ✓               |

`then` 句はあまり使ってこなかったけど、用法・用量を守って正しく使おうと思います。

# 参考

https://zenn.dev/kaleidot725/articles/2021-11-13-dart-future

https://zenn.dev/iwaku/articles/2020-12-19-iwaku