Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dart Errors in multi-level asynchronous code

Consider the following code

import 'dart:async';

Future main() async {
  try {
    print("trying");
    await doSomething();
    print("success");
  } catch (e) {
    print("caught");
  }
}

Future<int> doSomething() async {
  await doSomethingElse();
  return 5;
}

Future<int> doSomethingElse() async {
  throw new Exception();
}

When run, the exception thrown in doSomethingElse() is caught up in main(), and everything works as expected. But, say the person who wrote the doSomething() method didn't realize that doSomethingElse() was asynchronous, and instead wrote the follow (note the missing await).

Future<int> doSomething() async {
  doSomethingElse();
  return 5;
}

Now the exception isn't caught at all. Rather, the output now looks like this:

trying
success
Unhandled exception:
Uncaught Error: Exception
Stack Trace:
#0      doSomethingElse.<doSomethingElse_async_body> (file:///C:/code/test.dart:19:7)
#1      Future.Future.<anonymous closure> (dart:async/future.dart:118)
<snip>

What's happening is that doSomething() is returning immediately, and then sometime later, in another context, doSomethingElse() is throwing its error, stopping all execution immediately. I know that an answer to this might be "Well, don't do that then." but I'm considering cases where I might not have control over the methods I'm calling (say if they are part of a library).

This situation leads to a couple of related questions:

  • As the author of main(), is there any way I can be certain that my call to doSomething() won't end with an unhandled exception? Or am I dependent on the author of doSomething() to make sure all possible exceptions are handled or propagated to the returned Future? Is there a way to attach some sort of global error handler that can catch errors from abandoned Futures?
  • As the author of doSomething(), if I don't want to wait on doSomethingElse() (say it writes to a log for example, so I neither need the output nor do I need to worry about handling errors). Is there anything I can do to prevent errors in doSomethingElse() from halting the program other than wrapping every call of it a try/catch block (which can be cumbersome an easily overlooked)?
  • As the author of doSomethingElse(), is there some pattern I can use which allows me to throw Exceptions in a way that callers who wait for the Future to complete can handle that Exception themselves whereas callers that don't wait for the Future don't have to worry about catching the Exception? My best thought in that regard is to return a special object rather than throwing an Exception, but that adds a lot of extra cruft and makes the method much harder use.

Note: I'm using async/await syntax here, but the question should be equally relevant for a more strictly Future based construction (where you return a new Future in doSomething() instead of .then()ing off the one from doSomethingElse()

like image 816
Michael Fenwick Avatar asked Jan 06 '15 03:01

Michael Fenwick


People also ask

Is Dart synchronous or asynchronous?

Dart uses Future objects to represent asynchronous operations.

What are asynchronous errors?

An Asynchronous error happens independent of user program like an error in hardware, OS of CPU calls error OBs to handle it.A synchronous error happens when a problem is while processing the user program.

Which type of error prevents the code from executing Dart?

Which type of error prevents the code from executing in Dart? The main objective of the exception is to handle the run-time error and prevent the program from terminating abruptly. Every exception in the Dart is a subtype of the pre-defined class Exception.


2 Answers

Uncaught asynchronous errors are handled to the current zone's error handler. What you are seeing is the root-zone's error handler reporting the error as uncaught, which also terminates the isolate.

What you want is to introduce a different error handler for your code, by running it through runZoned with an error handler:

import "dart:async";
main() {
  runZoned(() async {
    try {
      print("trying");
      await doSomething();
      print("success");
    } catch (e) {
      print("caught");
    }
  }, onError: (e, s) {
    print("uncaught");
  });
}
like image 51
lrn Avatar answered Sep 23 '22 13:09

lrn


Like Greg pointed in its comment you can use Zones to catch unexpected errors from async code.

like image 27
Alexandre Ardhuin Avatar answered Sep 23 '22 13:09

Alexandre Ardhuin