Dart Generators

Summary: in this tutorial, you will learn about Dart Generators including synchronous and asynchronous generators.

Introduction to the Dart Generators

In Dart, generators are functions that produce a sequence of values. Unlike lists, generators generate a sequence of values on-demand without computing the entire sequence upfronts. Dart supports two types of generators:

  • Synchronous generators
  • Asynchronous generators

Generators are useful when dealing with large or infinite sequences of values because they avoid the need to compute and store the entire sequence upfront.

Synchronous generators

Synchronous generators are functions that generate values synchronously, once at a time, and pause their execution until the next value is requested.

To create a synchronous generator, you follow these steps:

  • First, define a function that returns an Iterable<T>.
  • Second, use the keyword sync* in the function header.
  • Third, use the yield keyword to generate a value of the sequence.

For example, the following defines a synchronous generator that generates a sequence of numbers from start (inclusive) to end (exclusive):

Iterable<int> range(int start, int end) sync* {
  for (var i = start; i < end; i++) {
    yield i;
  }
}

void main() {
  var numbers = range(1, 5);
  for (var number in numbers) {
    print(number);
  }
}Code language: Dart (dart)

Output:

1
2
3
4Code language: plaintext (plaintext)

How it works:

First, defines a synchronous generator function called range that takes two parameters: start and end. It returns an Iterable<int> that represents a sequence of integers between start (inclusive) and end (exclusive).

The sync* keyword indicates that the range function is a synchronous generator.

Inside the range function, we use a for loop to iterate over the range of values from start to end.

In each iteration of the loop, we use the yield keyword to produce the current value of i. The yield statement temporarily suspends the execution of the generator and provides the current value to the consumer of the generator.

After yielding a value, the generator resumes execution and continues to the next iteration of the loop.

Once the loop completes, the generator implicitly ends, and no further values are generated.

Second, define the main function as the entry point of the program.

In the main function, the range function is called with arguments 1 and 5, creating an Iterable<int> named numbers.

The numbers iterable represents the sequence of numbers from 1 to 4 (as the end value is exclusive).

We use a for-in loop is used to iterate over each number in the numbers iterable. In each iteration of the loop, the we assign current number to the variable number and use print function to output the value of number.

The loop continues until all numbers in the numbers iterable have been processed. Once the loop completes, the program execution ends.

Asynchronous generators

Asynchronous generators generate values asynchronously and allow for non-blocking operations. To create an asynchronous generator, you follow these steps:

  • First, define a function that returns a Stream<T>.
  • Second, use the keyword async* in the function header.
  • Third, use the yield await keywords to generate a value of the sequence.

For example, the following asynchronous generator generates a sequence of numbers from start to end every second:

import 'dart:async';

Stream<int> range(int start, int end) async* {
  for (var i = start; i < end; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield await i;
  }
}

Future<void> main() async {
  var stream = range(1, 5);
  stream.listen(
    (number) => print(number),
  );
}Code language: Dart (dart)

Output:

1
2
3
4Code language: plaintext (plaintext)

How it works:

First, import the dart:async library, which includes classes and utilities for asynchronous programming in Dart:

import 'dart:async';Code language: JavaScript (javascript)

Second, define an asynchronous generator function called range that takes two parameters: start and end. It returns a Stream<int> that represents a stream of integers between start (inclusive) and end (exclusive):

Stream<int> range(int start, int end) async* {
  for (var i = start; i < end; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield await i;
  }
}Code language: JavaScript (javascript)

In this function, the async* keyword indicates that the range function is an asynchronous generator. It uses a for loop is used to iterate over the range of values from start to end.

In each iteration of the loop, the await Future.delayed(Duration(seconds: 1)) statement introduces a delay of 1 second. The delay ensures that each value in the stream is produced asynchronously at a 1-second interval.

After the delay, the yield keyword is used to produce the current value of i. The await keyword is used to wait for the value of i.

The yield statement temporarily suspends the execution of the generator and provides the current value to the consumer of the stream. After yielding a value, the generator resumes execution and continues to the next iteration of the loop.

Once the loop completes, the generator implicitly ends, and no further values are produced in the stream.

Third, define the main() function which is the entry point of the program. In the main function, we call the range function with arguments 1 and 5, creating a Stream<int> named stream.

The stream represents the asynchronous stream of numbers from 1 to 4 (as the end value is exclusive) emitted at 1-second intervals.

We call the listen method on the stream to register a listener that will handle the events emitted by the stream.

Inside the listen method, we provide an anonymous function as the callback to handle each number emitted by the stream. The callback function uses the print function to output the value of number.

The listener continues to receive and process events emitted by the stream until the stream is closed.

Once all events have been processed and the stream is closed, the program execution ends.

Summary

  • Use synchronous and asynchronous generators to generate sequences of values.
  • Use generators to handle large or infinite sequences of values.
Was this tutorial helpful ?