🎓 A1Learner
🔍
0 of 24 lessons completed

Dart Stream Generators

A stream generator is a function marked async* that produces stream values with yield. Code reads those values with await for, one at a time as they arrive.

Counting with yield

countUp yields 1, 2, and then 3. await for prints each value as it arrives.

import 'dart:async';

Stream<int> countUp(int n) async* {
  for (var i = 1; i <= n; i++) {
    yield i;
  }
}

Future<void> main() async {
  await for (var v in countUp(3)) {
    print(v);
  }
}

Handing over a whole stream with yield*

sequence yields start, hands over both values from labels with yield*, and then yields end.

import 'dart:async';

Stream<String> labels() async* {
  yield '7';
  yield '8';
}

Stream<String> sequence() async* {
  yield 'start';
  yield* labels();
  yield 'end';
}

Future<void> main() async {
  await for (var v in sequence()) {
    print(v);
  }
}

A list is not a stream

await for is given a list here. The compiler refuses the program, and the first error line is the whole stated output.

import 'dart:async';

Future<void> main() async {
  var nums = [1, 2, 3];
  await for (var n in nums) {
    print(n);
  }
}

An error ends the generator

risky yields two values and then throws. The catch block prints the message, and the program carries on to print after.

import 'dart:async';

Stream<int> risky() async* {
  yield 1;
  yield 2;
  throw StateError('too far');
}

Future<void> main() async {
  try {
    await for (var v in risky()) {
      print('v $v');
    }
  } on StateError catch (e) {
    print('caught ${e.message}');
  }
  print('after');
}

Example

import 'dart:async';

Stream<int> squares(int n) async* {
  for (var i = 1; i <= n; i++) {
    yield i * i;
  }
}

Future<void> main() async {
  var total = 0;
  await for (var s in squares(4)) {
    total += s;
    print('square $s');
  }
  print('total $total');
}

Key points

  • The async* mark on a function makes it a stream generator, and the function returns a Stream.
  • yield hands one value to the listeners of that stream.
  • await for reads a stream inside an async function, running its body once for each value.
  • The await for loop finishes when the generator returns, and the code after the loop runs next.
  • yield* hands over every value from another stream in one step, and the generator then continues.
  • A throw inside a generator ends the stream with an error, and try with catch around await for catches that error.

In this example, the squares generator yields 1, 4, 9, and 16. await for prints each square and adds it to total by hand, and the last print shows the total 30.

Note

await for needs a stream, and putting a list in its place is a compile error instead of a running program.

Try it Yourself

Edit the code, then press Run.

Running is simulated · DartPad

Exercise

Which mark turns a function into a stream generator?