Why catchError is not able to catch the error?

Viewed 1376

Minimal reproducible code:

Future<void> foo() async => throw Future.error('Foo');

void main() {
  foo().catchError(print);
}

I'm still seeing this error on the console

Dart Unhandled Exception: Foo, stack trace:

If I throw FlutterError(...), the error is caught but Future.error(...) fails. The reason is that the error coming out is itself a Future, but how can I handle that?

1 Answers

There are two problems in your code:

Async marked methods are running sync until first await

Your async marked method is in fact not really running as another event on the event queue until the first await is happening.

In this case, it means the exception is actually thrown before we have time to assign any error handling and we are therefore getting an error for that. This behavior can be found in the documentation of catchError:

Note that futures don't delay reporting of errors until listeners are added. If the first catchError (or then) call happens after this future has completed with an error then the error is reported as unhandled error.

https://api.dart.dev/stable/2.12.2/dart-async/Future/catchError.html

The fix for this is to actually make an await before throwing the exception so lets do that:

Future<void> foo() async {
  await Future<void>.value();
  throw Future.error('Foo');
}

void main() {
  foo().catchError(print);
}

But we are now getting the following error as output which we need to look into:

Instance of 'Future<dynamic>'
Unhandled exception:
Foo

Throw inside async block converts object to Future error

The Instance of 'Future<dynamic>' is a line which comes from the print method which are trying to print the error. As we can see, we are somewhat getting a Future object here. This object is actually the object created from Future.error('Foo').

In Dart we are allowed to throw whatever object we want. In async marked method, this object is going to be packed inside a Future.error. So in your case, what you actually have done is Future.error(Future.error('Foo')).

You are then handling the outer error but not the inner one. If we do this instead:

Future<void> foo() async {
  await Future<void>.value();
  throw Future.error('Foo');
}

Future<void> main() async {
  await foo().catchError((Object obj) => (obj as Future).catchError(print));
}

This will return:

Foo

Since we are now calling catchError on the Future created by Future.error('Foo').

Related