Roadmap.shで学ぶFlutter - Advanced Dart - [Core Libraries②: async]

# 導入

前回に続いて、[roadmap.shのFlutter](https://roadmap.sh/flutter)のAdvanced DartとしてCore Librariesの項目の2番目の`async`についてです。

FlutterはAPIサーバーとのやりとりも多いと考えられるので、通信処理においてasyncを利用することはしばしばあると思われます。
このasyncをマスターすることはモバイルアプリ開発において重要であることは間違いありません。では、内容に入っていきます。

https://dart.dev/libraries/dart-async

# dart:async
## 非同期プログラミングとDartのFutureおよびStream

Dartでは、非同期プログラミングによく使われるコールバック関数の代わりに、`Future` と `Stream` オブジェクトを使用します。

`Future` は将来のある時点で提供される結果の約束のようなものであり、`Stream` はイベントなどの一連の値を取得する方法です。

これらのオブジェクトは `dart:async` ライブラリに含まれています。

:::note info
Dart言語は、`async` や `await` といったキーワードを使用した非同期コーディングをサポートしています。`Future` や `Stream` APIを直接使う必要は必ずしもありません。また、`dart:core` がこれらのクラスをエクスポートしているため、`dart:async` をインポートする必要はありません。
:::

## Future

`Future` オブジェクトは、**非同期メソッドによって返されるオブジェクト**としてDartライブラリに広く存在します。`Future` が完了すると、その値が使用可能になります。

### `await` の使用

`Future` API を直接使用する前に、`await` を使うことを検討してください。`await` 式を使用するコードは、`Future` API を使うコードよりも理解しやすいことがあります。

```dart
Future<void> runUsingAsyncAwait() async {
  // ...
  var entryPoint = await findEntryPoint();
  var exitCode = await runExecutable(entryPoint, args);
  await flushThenExit(exitCode);
}
```

`async` 関数は、`Future` からの例外をキャッチすることができます。

```dart
var entryPoint = await findEntryPoint();
try {
  var exitCode = await runExecutable(entryPoint, args);
  await flushThenExit(exitCode);
} catch (e) {
  // エラー処理...
}
```

### 基本的な使用法

`then()` メソッドを使用して、`Future` が完了したときに実行されるコードをスケジュールできます。

```dart
httpClient.read(url).then((String result) {
  print(result);
}).catchError((e) {
  // エラー処理...
});
```

### 複数の非同期メソッドのチェーン

`then()` メソッドは `Future` を返し、複数の非同期関数を特定の順序で実行するための便利な方法を提供します。

```dart
Future result = costlyQuery(url);
result
    .then((value) => expensiveWork(value))
    .then((_) => lengthyComputation())
    .then((_) => print('Done!'))
    .catchError((exception) {
  /* エラー処理... */
});
```

上記の例を `await` を使って書くと、以下のようになります。

```dart
try {
  final value = await costlyQuery(url);
  await expensiveWork(value);
  await lengthyComputation();
  print('Done!');
} catch (e) {
  /* エラー処理... */
}
```

### 複数の `Future` の待機

多くの非同期関数を呼び出してそれらがすべて完了するのを待つ必要がある場合は、`Future.wait()` メソッドを使用します。
この機能は便利そうです！

```dart
await Future.wait([
  deleteLotsOfFiles(),
  copyLotsOfFiles(),
  checksumLotsOfOtherFiles(),
]);
print('Done with all the long steps!');
```

### 複数の `Future` のエラーハンドリング

複数の非同期操作を並列に実行し、それぞれの結果を処理する場合には、`wait` メソッドを使用してエラーを処理することができます。

```dart
try {
  var results = await [delete(), copy(), errorResult()].wait;
  // 処理...
} on ParallelWaitError<List<bool?>, List<AsyncError?>> catch (e) {
  // エラーハンドリング...
}
```

個別の結果が必要な場合には、レコードを使用して `wait` メソッドを呼び出すことができます。

```dart
try {
  (int, String, bool) result = await (delete(), copy(), errorResult()).wait;
  // 処理...
} on ParallelWaitError<(int?, String?, bool?),
    (AsyncError?, AsyncError?, AsyncError?)> catch (e) {
  // エラーハンドリング...
}
```

## Stream

`Stream` オブジェクトは、DartのAPI全体にわたって使用されており、データのシーケンスを表します。たとえば、ボタンのクリックなどのHTMLイベントはストリームを使用して配信されます。また、ファイルをストリームとして読み取ることも可能です。

### 非同期forループの使用

非同期forループ (`await for`) は、`Stream` API の代わりに使用できる場合があります。

```dart
void main(List<String> arguments) async {
  if (await FileSystemEntity.isDirectory(searchPath)) {
    final startingDir = Directory(searchPath);
    await for (final entity in startingDir.list()) {
      if (entity is File) {
        searchFile(entity, searchTerms);
      }
    }
  } else {
    searchFile(File(searchPath), searchTerms);
  }
}
```

### ストリームデータのリスニング

`await for` を使用するか、`listen()` メソッドを使用してストリームにサブスクライブします。

```dart
submitButton.onClick.listen((e) {
  submitData();
});
```

### ストリームデータの変換

`transform()` メソッドを使用して、ストリームのデータ形式を変換できます。

```dart
var lines = inputStream
        .transform(utf8.decoder)
        .transform(const LineSplitter());
```

### エラーと完了の処理

`await for` を使用する場合、`try-catch` を使用してエラーを処理し、ストリームが閉じられた後に実行されるコードを記述します。

```dart
Future<void> readFileAwaitFor() async {
  var config = File('config.txt');
  Stream<List<int>> inputStream = config.openRead();

  var lines = inputStream
          .transform(utf8.decoder)
          .transform(const LineSplitter());
  try {
    await for (final line in lines) {
      print('Got ${line.length} characters from stream');
    }
    print('file is now closed');
  } catch (e) {
    print(e);
  }
}
```

`Stream` API を使用する場合、`onError` リスナーを登録してエラーを処理し、ストリームが閉じられた後のコードを `onDone` リスナーに登録します。

```dart
inputStream.transform(utf8.decoder).transform(const LineSplitter()).listen(
  (String line) {
    print('Got ${line.length} characters from stream');
  }, onDone: () {
    print('file is now closed');
  }, onError: (e) {
    print(e);
  });
```

## 終わりに

今回は、Dartの`async`に関する基本的な概念と使い方の紹介しました。非同期プログラミングは、モバイルアプリ開発において効率的かつスムーズなユーザーエクスペリエンスを提供するために重要な要素です。`Future`や`Stream`を活用して、安定したアプリケーションを構築していきたいです。