Search code examples
darterror-handlingtry-catchdart-async

Multiple async throws result in unhandled exception


I have an async method which in turn calls some other async methods which can throw errors. The problem is that the try-catch only handles the first exception thrown. I'm assuming that once a function gets an error from a future then it "fails" and stops its execution. So the remaining errors that get thrown thereafter are "orphaned" and thus become unhandled. Please correct me if I'm wrong.

Here is an example of the problem:

import 'dart:async';

Future<bool> asyncFuncThatThrows(Duration duration, String message) {
  return Future.delayed(
      duration, () => throw Exception('$message asyncFuncThatThrows'));
}

Future<void> asyncMethod() async {
  print('asyncMethod start');
  Future<bool> future1 = asyncFuncThatThrows(Duration(seconds: 0), 'first ');
  Future<bool> future2 = asyncFuncThatThrows(Duration(seconds: 0), 'second ');
  print('asyncMethod -> functions that throw executed');
  await future1;
  await future2;
  print('asyncMethod done');
}

void main() {
  runZoned(() async {
    try {
      print('Start');
      await asyncMethod();
    } catch (e) {
      print('Try/Catch in main: $e');
    }
    print('Done');
  });
}

And this is the output I get in my terminal (in DartPad surprisingly the unhandled exception doesn't appear... bonus question):

# dart run async_test.dart
Start
asyncMethod start
asyncMethod -> functions that throw executed
Try/Catch in main: Exception: first  asyncFuncThatThrows
Done
Unhandled exception:
Exception: second  asyncFuncThatThrows
#0      asyncFuncThatThrows.<anonymous closure> (file:///Users/amateo/code/flutter-projects/async_test.dart:5:23)
#1      new Future.delayed.<anonymous closure> (dart:async/future.dart:423:39)
#2      _rootRun (dart:async/zone.dart:1391:47)
#3      _CustomZone.run (dart:async/zone.dart:1301:19)
#4      _CustomZone.runGuarded (dart:async/zone.dart:1209:7)
#5      _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1249:23)
#6      _rootRun (dart:async/zone.dart:1399:13)
#7      _CustomZone.run (dart:async/zone.dart:1301:19)
#8      _CustomZone.bindCallback.<anonymous closure> (dart:async/zone.dart:1233:23)
#9      Timer._createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:18:15)
#10     _Timer._runTimers (dart:isolate-patch/timer_impl.dart:398:19)
#11     _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:429:5)
#12     _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)

I was expecting the try-catch would catch all the exceptions. How can I be sure to catch all the exceptions thrown by the asynchronous calls?


Solution

  • Yes, the problem is that you are starting two async operations and are getting two Future objects which you then later on start awaiting the first one. Issue is then, that the first Future fails and the rest of your asyncMethod() will not be executed which means we never subscribe to the future2 which have also failed.

    Dart then crashes since you have unhandled async errors.

    A possible solution is to await both Future objects at the same time by doing:

    await (future1, future2).wait;
    

    Now, your program will output the following where we can see both errors gets collected together:

    Start
    asyncMethod start
    asyncMethod -> functions that throw executed
    Try/Catch in main: ParallelWaitError(2 errors): Exception: first  asyncFuncThatThrows
    Done
    

    For DartPad, I don't really know what is going on but it feels like a bug that should be reported. But again, now sure. :)