はじめに
Dart の非同期処理といえば async
/ await
。
しかし、「結果が1回しか返せない」 という制約に物足りなさを感じたことはありませんか?
「3秒おきにセンサーの値を送信したい」
「APIからストリームでログを受け取りたい」
「カウントダウンをリアルタイムでUIに反映したい」
── そう、これらのケースにピッタリなのが async *
です!
async*
とは?
async*
は「非同期で値を順次生成する」ための構文。
普通の async
が Future
を返すのに対し、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秒ごとに「ぽこっ…ぽこっ…」と数字が流れてくるようなイメージです。
yield
と yield*
の違い
キーワード | 意味 | 例 |
---|---|---|
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 秒
まるで小さなタイマー職人。
async
と async*
の違いまとめ
項目 | 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*
を使おう。」