Dart Tutorial

Dart Lesson 75 of 102 4 min read

Generators in Dart: sync*, async*, yield and yield*

Learn generator functions in Dart: produce values lazily with sync* and async*, use yield and yield*, and build infinite sequences.

On this page

A generator is a function that produces a sequence of values one at a time, on demand. It pauses after each value and resumes when the next is requested. Dart has two kinds:

KeywordReturnsValues arrive
sync*Iterable<T>Immediately, when asked
async*Stream<T>Over time

In both, yield hands out one value.

A synchronous generator #

Iterable<int> countTo(int max) sync* {
  for (var i = 1; i <= max; i++) {
    yield i;
  }
}

void main() {
  for (final n in countTo(4)) {
    print(n);
  }
  print(countTo(3).toList());
}
1
2
3
4
[1, 2, 3]

Generators are lazy #

The body runs only as far as needed to produce the value being asked for.

Iterable<int> numbers() sync* {
  print('  producing 1');
  yield 1;
  print('  producing 2');
  yield 2;
  print('  producing 3');
  yield 3;
}

void main() {
  final firstTwo = numbers().take(2);
  print('Nothing has run yet');

  for (final n in firstTwo) {
    print('got $n');
  }
}
Nothing has run yet
  producing 1
got 1
  producing 2
got 2

“producing 3” never appears. The generator stopped as soon as the consumer had enough.

Infinite sequences #

Because values are made on demand, a generator can describe a sequence with no end. The consumer decides how much to take.

Iterable<int> fibonacci() sync* {
  var a = 0, b = 1;
  while (true) {
    yield a;
    (a, b) = (b, a + b);
  }
}

void main() {
  print(fibonacci().take(10).toList());
  print(fibonacci().firstWhere((n) => n > 1000));
  print(fibonacci().takeWhile((n) => n < 50).where((n) => n.isEven).toList());
}
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
1597
[0, 2, 8, 34]

Never call toList() or length on an infinite generator without take or takeWhile first.

yield*: hand over to another sequence #

yield* emits every value of another iterable or stream. It is what makes recursive generators neat.

Iterable<int> countDown(int from) sync* {
  if (from <= 0) return;
  yield from;
  yield* countDown(from - 1);
}

Iterable<String> menu() sync* {
  yield 'Starters';
  yield* ['Soup', 'Salad'];
  yield 'Mains';
  yield* ['Dal bhat', 'Momo'];
}

void main() {
  print(countDown(5).toList());
  print(menu().toList());
}
[5, 4, 3, 2, 1]
[Starters, Soup, Salad, Mains, Dal bhat, Momo]

Walking a tree #

class Folder {
  final String name;
  final List<Folder> children;
  Folder(this.name, [this.children = const []]);

  Iterable<String> paths([String prefix = '']) sync* {
    final path = '$prefix/$name';
    yield path;
    for (final child in children) {
      yield* child.paths(path);
    }
  }
}

void main() {
  final root = Folder('home', [
    Folder('docs', [Folder('work'), Folder('personal')]),
    Folder('photos'),
  ]);

  root.paths().forEach(print);
}
/home
/home/docs
/home/docs/work
/home/docs/personal
/home/photos

An asynchronous generator #

async* functions can await between values. They return a stream.

Stream<int> ticker(int count) async* {
  for (var i = 1; i <= count; i++) {
    await Future.delayed(Duration(milliseconds: 300));
    yield i;
  }
}

Stream<String> loadAllPages() async* {
  for (var page = 1; page <= 3; page++) {
    await Future.delayed(Duration(milliseconds: 200)); // pretend network call
    yield* Stream.fromIterable(['item ${page}a', 'item ${page}b']);
  }
}

Future<void> main() async {
  await for (final t in ticker(3)) {
    print('tick $t');
  }
  print(await loadAllPages().toList());
}
tick 1
tick 2
tick 3
[item 1a, item 1b, item 2a, item 2b, item 3a, item 3b]

When the listener cancels, an async* function stops at its next yield, so no further work is done.

Generator or a plain list? #

Build a list whenUse a generator when
The data is small and all of it is neededThe sequence is large, or infinite
You need random access with [i]Values are expensive and only some may be used
It will be read many timesIt will be read once, in order

Remember that a sync* iterable is re-run each time it is iterated. Call toList() once if you need to go through the values repeatedly.

Try it yourself #

Write Iterable<int> primes() as an infinite sync* generator. Print the first 15 primes, then the first prime above 1,000. Then write an async* version that emits one prime every 100 ms and stop it after five with take(5).

Practise in the playground Updated by Santosh Adhikari