How to keep generator function yielding values after throwing an error?

Viewed 629

In Dart language, how can I create an error in a generator function while keeping it creating values?

import 'dart:math' show Random;

main() {
  sum20RandomNumbers().then((sum) {
    print("Sum: $sum");
  });
}

Future<double> sum20RandomNumbers() async {
  var randomStream = generateRandomNumbers().handleError((error) {
    print('Ignore large number: ${error}');
    // How can I ignore error here?
  });

  int count = 0;
  double sum = 0;
  await for (var n in randomStream) {
    print(n);
    sum += n;
    if (++count >= 20) // sum 20 numbers at most
      break;
  }
  return sum;
}

Stream<double> generateRandomNumbers([int seed]) async* {
  final random = Random(seed);
  while (true) {
    final nextDouble = random.nextDouble();
    if (nextDouble > 0.8) {
      throw Exception('$nextDouble');
      // how can I keep generating next random numbers?
    }
    yield nextDouble;
  }
}

The above code shows the intent of sum20RandomNumbers() is to sum exactly 20 random numbers up, but it will be stopped after an Exception being thrown in generator function generateRandomNumbers(). How to keep generator function yielding values after throwing an error?

1 Answers

You cannot do that for sync* functions.

The only way an Iterable can report an error is by making the Iterator's moveNext method throw. To do that, a sync* method must throw. When a sync* function throws, it terminates the body of the function, which makes it impossible for that body to continue and produce more values.

The same thing happens for async* functions which throw, but there you also have a different way to emit an error. Because a stream can report errors as part of the stream, it is possible to emit errors and continue, you just can't do it by throwing.

Stream<double> generateRandomNumbers([int seed]) async* {
  final random = Random(seed);
  while (true) {
    final nextDouble = random.nextDouble();
    if (nextDouble > 0.8) {
      yield* Future<double>.error(Exception('$nextDouble')).asStream();
      continue;
    }
    yield nextDouble;
  }
}    

The trick is to yield* a stream which contains an error. You can do that in many different ways.

yield* Stream.error(theError);

This constructor was added in Dart 2.5 and is the easiest approach. Other alternatives are:

yield* Future<double>.error(theError).asStream();

or

yield* () async* { throw theError; } ();

or

yield* (StreamController<double>()..addError(theError)..close()).stream;
Related