Passing Exceptions up the stack in Dart from async to sync methods - dart

So, coming from Javascript world, I can handle exceptions that are thrown however deep down the stack. Doing the same in Dart doesn't work. I'm not sure how to pass exceptions up, to be handled at the root of the stack.
willThrow() async {
throw Exception('Im an exception');
}
init() async {
final illNeverExist = await willThrow();
print(illNeverExist);
}
main() {
try {
init();
} catch(err) {
print(err);
}
}
^^^ This totally works in javascript.
In 'init', even if I wrap that in a try catch, and throw that error, I always get an uncaught exception.
init() async {
try {
final illNeverExist = await willThrow();
print(illNeverExist);
} catch(err) {
throw err
}
}
How do you pass async exceptions up the stack in dart?!

The try-catch block in your main function doesn't wait for your asynchronous init function to complete. Consequently, when init does complete, its exception will no longer be caught.
You can fix this by making main async and using await init();, or you can use Future.catchError to directly register an error callback on the returned Future.

Related

Exceptions not propagating up automatically in Dart

Edit:
This was a false alarm. See my answer post below for details.
Original post:
I've been hitting my head against this for a while now, so I made some test code to confirm the behaviour that I'm seeing.
I have this code, where there's a chain of async methods, and one of them deep down is throwing an error.
Future test() async {
await test2();
}
Future test2() async {
await test3();
}
Future test3() async {
await test4();
}
Future test4() async {
throw "a test failure";
}
Then, elsewhere in an async method, I made this call:
try {
await test();
} catch (e) {
print(e);
}
What I'm seeing is that my catch statement isn't being hit.
Now if I instead add a catch within each test method and rethrow the error, instead now the code flows and my topmost catch statement works:
Future test() async {
try {
await test2();
} catch (e) {
print("test");
throw e;
}
}
Future test2() async {
try {
await test3();
} catch (e) {
print("test2");
throw e;
}
}
Future test3() async {
try {
await test4();
} catch (e) {
print("test3");
throw e;
}
}
Future test4() async {
throw "a test failure";
}
What am I missing? Why doesn't my topmost catch statement catch the error unless I explicitly catch and rethrow the error at each level?
For reference, I'm currently running this version of Dart:
Dart SDK version: 2.12.3 (stable) (Wed Apr 14 11:02:39 2021 +0200) on "macos_x64"
Okay, so in the end, it looks like the catch statement was working. The confusion was around how the debugger in VS Code was reacting to it.
When individual try/catch statements are included the debugger flows through the code and ignores the exception. However, when there's only the try/catch at the topmost level, VS Code halts at the exception.
However, if you tell the debugger to continue, it will then hit bubble up to the outer catch statement.
So in the end it's a false alarm and unintuitive behaviour from my debugger.

Difference between throwing and returning a Future.error

Minimal reproducible code
Future<void> foo() async {
final fooError = Future.error('FooError');
return Future.error(fooError);
}
Future<void> bar() async {
await Future(() {});
throw Future.error('BarError');
}
void main() {
foo().catchError((e) => (e as Future).catchError(print)); // Error is NOT handled.
bar().catchError((e) => (e as Future).catchError(print)); // Error is handled.
}
As you can see, BarError is handled but not the FooError. AFAIK, when you mark a method async and use throw, the error is wrapped in a Future.error(...). But my first code doesn't work.
Future.error() creates a Future that completes with an error during a future microtask. This means that if the created Future doesn't have a listener when the microtask is executed, it's considered unhandled and will cause the program to terminate. I'm not 100% sure what's triggering the microtask to be executed, but the microtask queue is always executed and drained until it's empty before executing the next event in the isolate's event loop.
However, those details aren't terribly important. To fix this issue, update your example to not wrap your Future.error('FooError') with another Future.error(...), things work as expected:
Future<void> foo() async {
return Future.error('FooError');
}
Future<void> bar() async {
await Future(() {});
throw 'BarError';
}
void main() {
foo().catchError(print); // Error is now handled.
bar().catchError(print); // Error is handled.
}

The modifier async can not by applied to the body of a setter

How can I use hashIt function in setter if editor gives this error
The modifier async can not by applied to the body of a setter
Future<String> hashIt(String password) async {
return await PasswordHash.hashStorage(password);
}
set hashPass(String pass) async { // error here
final hash = await hashIt(pass);
_hash = hash;
}
compiller message: Error: Setters can't use 'async', 'async*', or 'sync*'.
The reason a setter cannot be async is that an async function returns a future, and a setter does not return anything. That makes it highly dangerous to make a setter async because any error in the setter will become an uncaught asynchronous error (which may crash your program).
Also, being async probably means that the operation will take some time, but there is no way for the caller to wait for the operation to complete. That introduces a risk of race conditions.
So, it's for your own protections.
If you need to do something asynchronous inside the setter anyway, perhaps log something after doing the actual setting, you have a few options.
The simplest is to just call an async helper function:
set foo(Foo foo) {
_foo = foo;
_logSettingFoo(foo);
}
static void _logSettingFoo(Foo foo) async {
try {
var logger = await _getLogger();
await logger.log("set foo", foo);
logger.release(); // or whatever.
} catch (e) {
// report e somehow.
}
}
This makes it very clear that you are calling an async function where nobody's waiting for it to complete.
If you don't want to have a separate helper function, you can inline it:
set foo(Foo foo) {
_foo = foo;
void _logSettingFoo() async {
...
}
_logSettingFoo();
}
or even
set foo(Foo foo) {
_foo = foo;
() async {
...foo...
}();
}

flutter catching errors of nested async methods

I have this method.
asyncFunction1() async {
Firestore.instance.runTransaction((transaction){
var first = await transaction.something;
var second = await secondInside();
});
}
Now I want to call this method, and catch every error that happens inside. How would I propagate errors so that
try {asyncFunction1()}
catch(e){}
catches all errors that happened inside runTransaction?
Your inner function misses an async to be able to use await. If you add it, you can use try/catch
asyncFunction1() {
Firestore.instance.runTransaction((transaction) async {
try {
var first = await transaction.something;
var second = await secondInside();
} catch(e) {
...
}
});
}

How should i use Dart Isolate unhandledExceptionCallback?

I'm trying to use Isolates in my Dart webapp but i can't seem to make the error callback argument work.
I have a very basic code which is being run in Dartium.
import "dart:isolate";
void main() {
print("Main.");
spawnFunction(test, (IsolateUnhandledException e) {
print(e);
});
}
void test() {
throw "Ooops.";
}
I never see anything than "Main." printed on the console. Am i doing something wrong or is this broken right now?
The error-callback will be executed in the new isolate. Therefore it cannot be a dynamic closure but needs to be a static function.
I haven't tested it, but this should work:
import "dart:isolate";
bool errorHandler(IsolateUnhandledException e) {
print(e);
return true;
}
void test() {
throw "Ooops.";
}
void main() {
// Make sure the program doesn't terminate immediately by keeping a
// ReceivePort open. It will never stop now, but at least you should
// see the error in the other isolate now.
var port = new ReceivePort();
print("Main.");
spawnFunction(test, errorHandler);
}
Note: In dart2js this feature is still unimplemented. Older versions just ignored the argument. Newer versions will throw with an UnimplementedError.

Resources