0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Dart】Dartの `async*` 完全解説 ── Streamを優雅に操る非同期ジェネレーター

Posted at

はじめに

Dart の非同期処理といえば async / await
しかし、「結果が1回しか返せない」 という制約に物足りなさを感じたことはありませんか?

「3秒おきにセンサーの値を送信したい」
「APIからストリームでログを受け取りたい」
「カウントダウンをリアルタイムでUIに反映したい」

── そう、これらのケースにピッタリなのが async * です!


async* とは?

async* は「非同期で値を順次生成する」ための構文。
普通の asyncFuture を返すのに対し、async*Stream を返します。

Stream<int> counter() async* {
  for (int i = 1; i <= 3; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i; //  値をひとつずつストリームに流す
  }
}

使うときはこう

void main() async {
  await for (final value in counter()) {
    print(value);
  }
}

出力結果:

1
2
3

1秒ごとに「ぽこっ…ぽこっ…」と数字が流れてくるようなイメージです。


yieldyield* の違い

キーワード 意味
yield 単体の値を返す yield 1;
yield* 別のStreamやIterableを「展開」して返す yield* anotherStream;
Stream<int> sub() async* {
  yield 4;
  yield 5;
}

Stream<int> all() async* {
  yield 1;
  yield 2;
  yield* sub(); // sub() の全要素を展開
  yield 6;
}

出力:

1
2
4
5
6

例:非同期カウントダウン

「1秒ごとに残り時間を流す」ようなケースでは、async* は真価を発揮します。

Stream<int> countdown(int from) async* {
  for (int i = from; i >= 0; i--) {
    yield i;
    await Future.delayed(Duration(seconds: 1));
  }
}
await for (final t in countdown(3)) {
  print('残り $t 秒');
}

結果:

残り 3 秒
残り 2 秒
残り 1 秒
残り 0 秒

まるで小さなタイマー職人。


asyncasync* の違いまとめ

項目 async async*
戻り値 Future<T> Stream<T>
値の回数 1回だけ 複数回(ストリーム)
使うキーワード return yield
消費の仕方 await await for
よく使う場面 単発API呼び出し リアルタイムデータ/イベント通知

応用:イベントストリームを組み合わせる

例えば、センサーの値を5秒ごとに取得し続けたいとします。

Stream<double> sensorStream() async* {
  while (true) {
    final value = await readSensor();
    yield value;
    await Future.delayed(Duration(seconds: 5));
  }
}

Flutter ではこのストリームを StreamBuilder に渡せば、
UIが自動的にリアルタイム更新されます。

StreamBuilder<double>(
  stream: sensorStream(),
  builder: (context, snapshot) {
    if (!snapshot.hasData) return Text('Loading...');
    return Text('Sensor: ${snapshot.data!.toStringAsFixed(2)}');
  },
);

小ネタ:「StreamControllerで手動管理するより楽!」

もちろん、StreamController を使えば似たようなことはできます。
しかし async* はもっとシンプル。
内部でキャンセル・エラー伝播・完了通知まで自動でやってくれます。

// 手動だとこうなる
final controller = StreamController<int>();
void start() {
  Timer.periodic(Duration(seconds: 1), (timer) {
    controller.add(timer.tick);
    if (timer.tick >= 3) {
      controller.close();
      timer.cancel();
    }
  });
}

// async* なら一行で終わり
Stream<int> easyCounter() async* {
  for (int i = 1; i <= 3; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i;
  }
}

まとめ

ポイント 内容
async*Stream を返す非同期ジェネレーター
yield で1つずつ値を送る
yield* で他のStreamを展開できる
Flutterでは StreamBuilder と相性抜群
StreamController より簡潔・安全

async* は「非同期の物語を語るための構文」です。
未来を1つだけ返す Future に対して、Stream時の流れそのものを返します。

「非同期の世界で物語を紡ぎたいとき── async* を使おう。」

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?