目次

dart:async

非同期プログラミングではコールバック関数がよく使用されますが、Dartでは代替手段としてFutureStreamオブジェクトが提供されています。Futureは、将来のある時点で結果が提供されることを約束するようなものです。Streamは、イベントなどの一連の値を取得する方法です。Future、Streamなどは、dart:asyncライブラリ(APIリファレンス)にあります。

dart:asyncライブラリは、Webアプリとコマンドラインアプリの両方で動作します。これを使用するには、dart:asyncをインポートします

dart
import 'dart:async';

Future

#

Futureオブジェクトは、Dartライブラリ全体に、多くの場合、非同期メソッドによって返されるオブジェクトとして出現します。Futureが完了すると、その値を使用する準備ができています。

awaitの使用

#

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

次の関数を検討してください。この関数は、Futureのthen()メソッドを使用して、3つの非同期関数を順番に実行し、それぞれが完了するのを待ってから次の関数を実行します。

dart
void runUsingFuture() {
  // ...
  findEntryPoint().then((entryPoint) {
    return runExecutable(entryPoint, args);
  }).then(flushThenExit);
}

await式を使用した同等のコードは、より同期コードのように見えます。

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) {
  // Handle the error...
}

awaitと関連するDart言語機能の使用に関する詳細については、非同期プログラミングチュートリアルを参照してください。

基本的な使い方

#

then()を使用して、Futureが完了したときに実行されるコードをスケジュールできます。たとえば、Client.read()は、HTTPリクエストに時間がかかる可能性があるため、Futureを返します。then()を使用すると、そのFutureが完了し、約束された文字列値が利用可能になったときに、いくつかのコードを実行できます。

dart
httpClient.read(url).then((String result) {
  print(result);
});

Futureオブジェクトがスローする可能性のあるエラーまたは例外を処理するには、catchError()を使用します。

dart
httpClient.read(url).then((String result) {
  print(result);
}).catchError((e) {
  // Handle or ignore the error.
});

then().catchError()パターンは、try-catchの非同期バージョンです。

複数の非同期メソッドの連結

#

then()メソッドは、Futureを返し、特定の順序で複数の非同期関数を実行するのに役立つ方法を提供します。then()に登録されたコールバックがFutureを返す場合、then()は、コールバックから返されたFutureと同じ結果で完了するFutureを返します。コールバックが他のタイプの値を返す場合、then()は、その値で完了する新しいFutureを作成します。

dart
Future result = costlyQuery(url);
result
    .then((value) => expensiveWork(value))
    .then((_) => lengthyComputation())
    .then((_) => print('Done!'))
    .catchError((exception) {
  /* Handle exception... */
});

前の例では、メソッドは次の順序で実行されます。

  1. costlyQuery()
  2. expensiveWork()
  3. lengthyComputation()

次に、awaitを使用して記述された同じコードを示します。

dart
try {
  final value = await costlyQuery(url);
  await expensiveWork(value);
  await lengthyComputation();
  print('Done!');
} catch (e) {
  /* Handle exception... */
}

複数のFutureの待機

#

アルゴリズムで多くの非同期関数を呼び出し、すべてが完了するのを待ってから続行する必要がある場合があります。Future.wait()静的メソッドを使用して、複数のFutureを管理し、それらが完了するのを待ちます。

dart
Future<void> deleteLotsOfFiles() async =>  ...
Future<void> copyLotsOfFiles() async =>  ...
Future<void> checksumLotsOfOtherFiles() async =>  ...

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

Future.wait()は、提供されたすべてのFutureが完了すると完了するFutureを返します。提供されたFutureのいずれかが失敗した場合、結果またはエラーのいずれかで完了します。

複数のFutureのエラー処理

#

また、Futureのiterableまたはrecordに対する並列操作を待つこともできます。

これらの拡張機能は、提供されたすべてのFutureの結果値を含むFutureを返します。Future.waitとは異なり、エラーを処理することもできます。

コレクション内のいずれかのFutureがエラーで完了した場合、waitParallelWaitErrorで完了します。これにより、呼び出し元は個々のエラーを処理し、必要に応じて成功した結果を破棄できます。

各Futureからの結果値が不要な場合は、Futureのiterablewaitを使用します。

dart
void main() async {
  Future<void> delete() async =>  ...
  Future<void> copy() async =>  ...
  Future<void> errorResult() async =>  ...
  
  try {
    // Wait for each future in a list, returns a list of futures:
    var results = await [delete(), copy(), errorResult()].wait;

    } on ParallelWaitError<List<bool?>, List<AsyncError?>> catch (e) {

    print(e.values[0]);    // Prints successful future
    print(e.values[1]);    // Prints successful future
    print(e.values[2]);    // Prints null when the result is an error

    print(e.errors[0]);    // Prints null when the result is successful
    print(e.errors[1]);    // Prints null when the result is successful
    print(e.errors[2]);    // Prints error
  }

}

各Futureからの個々の結果値が必要な場合は、Futureのrecordwaitを使用します。これにより、Futureが異なる型になる可能性があるという追加の利点も提供されます。

dart
void main() async {
  Future<int> delete() async =>  ...
  Future<String> copy() async =>  ...
  Future<bool> errorResult() async =>  ...

  try {    
    // Wait for each future in a record, returns a record of futures:
    (int, String, bool) result = await (delete(), copy(), errorResult()).wait;
  
  } on ParallelWaitError<(int?, String?, bool?),
      (AsyncError?, AsyncError?, AsyncError?)> catch (e) {
    // ...
    }

  // Do something with the results:
  var deleteInt  = result.$1;
  var copyString = result.$2;
  var errorBool  = result.$3;
}

Stream

#

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

非同期forループの使用

#

場合によっては、Stream APIを使用する代わりに、非同期forループ(await for)を使用できます。

次の関数を検討してください。この関数は、Streamのlisten()メソッドを使用して、ファイルのリストをサブスクライブし、各ファイルまたはディレクトリを検索する関数リテラルを渡します。

dart
void main(List<String> arguments) {
  // ...
  FileSystemEntity.isDirectory(searchPath).then((isDir) {
    if (isDir) {
      final startingDir = Directory(searchPath);
      startingDir.list().listen((entity) {
        if (entity is File) {
          searchFile(entity, searchTerms);
        }
      });
    } else {
      searchFile(File(searchPath), searchTerms);
    }
  });
}

非同期forループ(await for)を含む、await式を使用した同等のコードは、より同期コードのように見えます。

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と関連するDart言語機能の使用に関する詳細については、非同期プログラミングチュートリアルを参照してください。

ストリームデータの監視

#

各値が到着したら取得するには、await forを使用するか、listen()メソッドを使用してストリームをサブスクライブします。

dart
// Add an event handler to a button.
submitButton.onClick.listen((e) {
  // When the button is clicked, it runs this code.
  submitData();
});

この例では、onClickプロパティは、送信ボタンによって提供されるStreamオブジェクトです。

1つのイベントのみに関心がある場合は、firstlast、またはsingleなどのプロパティを使用して取得できます。処理する前にイベントをテストするには、firstWhere()lastWhere()、またはsingleWhere()などのメソッドを使用します。

イベントのサブセットに関心がある場合は、skip()skipWhile()take()takeWhile()、およびwhere()などのメソッドを使用できます。

ストリームデータの変換

#

多くの場合、ストリームのデータを使用する前に、ストリームのデータの形式を変更する必要があります。transform()メソッドを使用して、異なるタイプのデータを含むストリームを生成します。

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

この例では、2つのトランスフォーマーを使用しています。最初に、utf8.decoderを使用して整数のストリームを文字列のストリームに変換します。次に、LineSplitterを使用して、文字列のストリームを個別の行のストリームに変換します。これらのトランスフォーマーは、dart:convertライブラリ(dart:convertセクションを参照)からのものです。

エラーと完了の処理

#

エラーと完了処理のコードをどのように指定するかは、非同期forループ (await for) を使用するか、Stream API を使用するかによって異なります。

非同期forループを使用する場合、エラーを処理するにはtry-catchを使用します。ストリームが閉じられた後に実行されるコードは、非同期forループの後に記述します。

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
var config = File('config.txt');
Stream<List<int>> inputStream = config.openRead();

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);
});

詳細情報

#

コマンドラインアプリでFutureとStreamを使用する例については、dart:ioドキュメントを参照してください。また、以下の記事とチュートリアルも参照してください。