2020/6/8 dart:async – asynchronous programming(stream))

Stream

Dart APIsのStreamオブジェクトは、複数の非同期処理の結果を表現します。例えば、ボタンのクリックのようなHTMLイベントはstreamを使って届けられます。ファイルもstreamとして読み込むことができます。

Using an asynchronous for loop

Stream APIの代わりに非同期なforループ(await for)を使うことができます。

sample1-1ではStreamクラスのlisten()メソッドでファイルのリストをリッスン(subscribe(購読))しています。listenメソッドの引数に、それぞれのファイルをサーチする関数リテラルを渡しています。

sample1-1の関数について考えましょう。

sample1-1

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

isDirectoryメソッドがFuture<bool>型を返す。

 –>

startingDir.list()がStream<FileSystemEntity>型を返す。

 –>

startingDir.list().listen()

 –>それぞれのイベントから受け取るファイル・ディレクトリなど(entity)を調べて、File型ならsearchFile()関数に渡す。

sample1-1と同じ挙動を示すコードをawaitを使って書きます。非同期なforループ(await for)を使うと、同期的なコードのように記述することができます。(sample1-2)

sample1-2

Future main(List<String> arguments) async {
  // ...
  if (await FileSystemEntity.isDirectory(searchPath)) {
    final startingDir = Directory(searchPath);
    await for (var entity in startingDir.list(
        recursive: argResults[recursive],
        followLinks: argResults[followLinks])) {
      if (entity is File) {
        searchFile(entity, searchTerms);
      }
    }
  } else {
    searchFile(File(searchPath), searchTerms);
  }
}

awaitに関しての情報とDart言語の関連する機能については、asynchronous programming codelab.もご覧ください。


Listening for stream data

(streamの結果の)値が届いた時にそれらを取得するためには、

–> await forを使う。

–> listen()メソッドを使ってstreamを購読(subscribe)する。

の二つの方法があります。

sample2-1

// Find a button by ID and add an event handler.
querySelector('#submitInfo').onClick.listen((e) {
  // When the button is clicked, it runs this code.
  submitData();
});

sample2-1では”submitInfo”ボタンのonClickプロパティがStreamインスタンスです。

もし一つのイベント(の結果)のみ必要なら、first,last,singleなどのプロパティを参照し、結果を取得することができます。結果を処理する前にテストしたい場合、firstWhere(),lastWhere(),singleWhere()などのメソッドを使うことができます。

streamの全イベントの一部分が必要な場合、skip(),skipWhile(),take(),takeWhile(),where()などのメソッドが使えます。


Transforming stream data

しばしば、streamの結果の値を使用する前に、結果の値のフォーマットを変更する必要があるシーンがあります。そのようなケースではtransform()メソッドを使用してデータの型が違うstreamを生成することができます。

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

上記のサンプルでは二つのtransformerを使っています。一番目はutf8.decorderで、Stream<int>→Stream<String>に変換します。二番目はLineSplitter()で、Stream<String>の各イベントの値を

工事中🏗


Handling errors and completion

エラーや完了を処理するコードの記述方法は、await forを使っているか、StreamAPIを使っているかによって異なってきます。

await forを使っている場合、try-catch文を使ってエラーハンドリングをします。streamが閉じた後に実行されるコードは、await forの後ろに来ます。

sample3-1

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

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

sample3-1で、変数linesにセットされるのは、config.txtの一行ずつの文字列が、各イベントの結果として返されるstream。

Stream APIを使う場合、listenメソッドのonError引数にリスナー(コールバック関数)を設定します。streamが終了した時に実行されるコードはonDone引数にリスナー(コールバック関数)として設定します(sample3-2)。

sample3-2

var config = File('config.txt');
Stream<List<int>> inputStream = config.openRead();

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

 

 

 

参考

https://dart.dev/guides/libraries/library-tour

https://dart.dev/guides/libraries/library-tour#stream

コメントを残す

メールアドレスが公開されることはありません。