Generators in Dart are a powerful feature that allows you to produce a sequence of values lazily, meaning values are generated on demand rather than all at once. This can be particularly useful when working with large datasets or streams of data where you don't want to load everything into memory at once.

Key Concepts

  1. Synchronous Generators: These generate values synchronously using the sync* keyword.
  2. Asynchronous Generators: These generate values asynchronously using the async* keyword.
  3. Yield: The yield keyword is used to produce a value in a generator function.

Synchronous Generators

A synchronous generator produces values one at a time and can be iterated over using a for loop or other iterable methods.

Example

Iterable<int> syncGenerator(int n) sync* {
  for (int i = 0; i < n; i++) {
    yield i;
  }
}

void main() {
  var numbers = syncGenerator(5);
  for (var number in numbers) {
    print(number);
  }
}

Explanation

  • The sync* keyword indicates that the function is a synchronous generator.
  • The yield keyword is used to produce each value.
  • The for loop in the main function iterates over the generated values.

Asynchronous Generators

An asynchronous generator produces values asynchronously, which is useful when dealing with I/O operations or other asynchronous tasks.

Example

Stream<int> asyncGenerator(int n) async* {
  for (int i = 0; i < n; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i;
  }
}

void main() async {
  await for (var number in asyncGenerator(5)) {
    print(number);
  }
}

Explanation

  • The async* keyword indicates that the function is an asynchronous generator.
  • The yield keyword is used to produce each value.
  • The await keyword is used to wait for asynchronous operations.
  • The await for loop in the main function iterates over the generated values asynchronously.

Practical Exercises

Exercise 1: Fibonacci Sequence

Write a synchronous generator function that produces the first n numbers in the Fibonacci sequence.

Solution

Iterable<int> fibonacci(int n) sync* {
  int a = 0, b = 1;
  for (int i = 0; i < n; i++) {
    yield a;
    int temp = a;
    a = b;
    b = temp + b;
  }
}

void main() {
  var fibNumbers = fibonacci(10);
  for (var number in fibNumbers) {
    print(number);
  }
}

Exercise 2: Asynchronous Data Fetching

Write an asynchronous generator function that fetches data from a list of URLs and yields the data.

Solution

import 'dart:async';

Stream<String> fetchData(List<String> urls) async* {
  for (var url in urls) {
    await Future.delayed(Duration(seconds: 1)); // Simulate network delay
    yield 'Data from $url';
  }
}

void main() async {
  var urls = ['url1', 'url2', 'url3'];
  await for (var data in fetchData(urls)) {
    print(data);
  }
}

Common Mistakes and Tips

  • Forgetting sync* or async*: Ensure you use sync* for synchronous generators and async* for asynchronous generators.
  • Blocking Code in Asynchronous Generators: Avoid blocking code in asynchronous generators; use await for asynchronous operations.
  • Using yield Outside Generators: The yield keyword can only be used inside generator functions.

Summary

In this section, you learned about Dart generators, including both synchronous and asynchronous generators. You saw how to use the sync* and async* keywords to create generators and how to use the yield keyword to produce values. You also practiced writing generator functions with practical exercises. Understanding generators will help you handle sequences of data more efficiently, especially in scenarios involving large datasets or asynchronous operations.

© Copyright 2024. All rights reserved