メインコンテンツにスキップ

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() は Future を返します。HTTP リクエストには時間がかかる場合があるためです。then() を使用すると、その Future が完了し、約束された文字列値が利用可能になったときにコードを実行できます。

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

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

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 を管理し、それらが完了するのを待つには、Future.wait() 静的メソッドを使用します。

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 の結果値を持つ Future を返します。Future.wait とは異なり、エラーを処理することもできます。

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

各 Future の結果値が *必要ない* 場合は、Future の *イテラブル* に対して wait を使用します。

dart
Future<int> delete() async => ...;
Future<String> copy() async => ...;
Future<bool> errorResult() async => ...;

void main() 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 の *レコード* に対して wait を使用します。これにより、Future の型が異なる場合でも対応できるという追加の利点があります。

dart
Future<int> delete() async => ...;
Future<String> copy() async => ...;
Future<bool> errorResult() async => ...;

void main() async {
  try {
    // Wait for each future in a record.
    // Returns a record of futures that you can destructure.
    final (deleteInt, copyString, errorBool) =
        await (delete(), copy(), errorResult()).wait;

    // Do something with the results...
  } on ParallelWaitError<
    (int?, String?, bool?),
    (AsyncError?, AsyncError?, AsyncError?)
  > catch (e) {
    // ...
  }
}

Stream

#

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

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

Stream データを受信する

#

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

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() のようなメソッドを使用できます。

Stream データを変換する

#

多くの場合、Stream のデータをそのまま使用する前に、その形式を変更する必要があります。transform() メソッドを使用して、異なるデータ型の Stream を生成します。

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

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

エラーと完了を処理する

#

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

非同期 for ループを使用する場合は、try-catch を使用してエラーを処理します。Stream が閉じられた後に実行されるコードは、非同期 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 リスナーを登録してエラーを処理します。Stream が閉じられた後にコードを実行するには、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 ドキュメントを確認してください。また、これらの記事とチュートリアルも参照してください。