gianmarcocalbi
gianmarcocalbi

Reputation: 313

Dart: Let an exception thrown inside a stream propagate and catch it in the caller

I would like to have an exception thrown inside stream to be caught and handled outside by the caller.

If I try with a standard try-catch block, like below, it does work: I get Uncaught Error: Exception.

By running the snippet code below in DartPad

void main() async {
  await for (var i in mainStream()) {
    print(i);
  }
}

Stream<int> subStreamA() async* {
  for (var i in List.generate(10, (i) => i)) {
    if (i == 2) {
      throw Exception();
    }
    yield i;
  }
}
Stream<int> subStreamB() async* {
  for (var i in List.generate(10, (i) => i + 10)) {
    yield i;
  }
}

Stream<int> mainStream() async* {
  try {
    yield* subStreamA();
    yield* subStreamB();
  } catch (e) {
    print('CAUGHT! :)');
    yield -1;
  }
}


The result is:

0
1
Uncaught Error: Exception

I would like the exception to be caught, resulting in:

0
1
CAUGHT! :)
-1

I had some "solution" in mind, but they're kind of horrible :( such as...

Stream<int> mainStream() async* {
  try {
    Exception? err;
    yield* subStreamA().handleError((e) {
      err = e;
    });
    if (err != null) throw err!;
    yield* subStreamB().handleError((e) {
      err = e;
    });
    if (err != null) throw err!;
  } catch (e) {
    print('CAUGHT! :)');
    yield -1;
  }
}

Is there a clean solution leveraging a normal try-catch without dirty tricks?

Upvotes: 1

Views: 267

Answers (1)

julemand101
julemand101

Reputation: 31299

The behavior of yield* for async* marked method is part of the Dart Language Design which describes this with:

The o stream is listened to, creating a subscription s, and for each event x, or error e with stack trace t, of s:

...

  • Otherwise, x, or e with t, are added to the stream associated with m in the order they appear in o.

https://dart.dev/guides/language/specifications/DartLangSpec-v2.10.pdf

So in easier terms, this means when we use yield* we are forwarding both events and errors (with stacktrace) from the Stream we are forwarding.

If you want to catch the exceptions, I think the easiest solution is to rewrite the use of yield* to await for loops like this:

Stream<int> mainStream() async* {
  try {
    await for (final event in subStreamA()) {
      yield event;
    }
    await for (final event in subStreamB()) {
      yield event;
    }
  } catch (e) {
    print('CAUGHT! :)');
    yield -1;
  }
}

With this change I get the following output:

0
1
CAUGHT! :)
-1

Upvotes: 3

Related Questions