Future<void> fetchUserOrder() async {
// Imagine that this function is fetching user info but encounters a bug
try {
return Future.delayed(const Duration(seconds: 2),
() => throw Exception('Logout failed: user ID is invalid'));
} catch(e) {
// Why exception is not caught here?
print(e);
}
}
void main() {
fetchUserOrder();
print('Fetching user order...');
}
It outputs
Fetching user order...
Uncaught Error: Exception: Logout failed: user ID is invalid
Which says the exception is not caught. But as you see, the throw Exception clause is surrounded by try catch.
The try-catch block will only catch exception of awaited Future. So you have to use await in your code:
Future<void> fetchUserOrder() async {
// Imagine that this function is fetching user info but encounters a bug
try {
return await Future.delayed(const Duration(seconds: 2),
() => throw Exception('Logout failed: user ID is invalid'));
} catch(e) {
// Why exception is not caught here?
print(e);
}
}
void main() {
fetchUserOrder();
print('Fetching user order...');
}
Related
I wrote the following code and encountered the error The provider AutoDisposeFutureProvider<Data>#d1e31(465-0041) was disposed before a value was emitted.
I thought it was strange, so I debugged FutureProvider's onDispose and found that it was disposed during the await of the API call, which is confirmed by the output of disposed!
class HogeNotifier extends StateNotifier<Hoge> {
onFormSubmitted(String input) async {
final value = await _reader(searchProvider(input).future); // The provider AutoDisposeFutureProvider<Data>#d1e31(465-0041) was disposed before a value was emitted.
// execute by using value
}
}
final searchProvider =
FutureProvider.autoDispose.family<Data, String>((ref, value) async {
ref.onDispose(() {
print("disposed!");
});
try {
final result = await dataSource.find(keyword: value); //call api asynchronously
return Future.value(result);
} on Exception catch (e) {
return Future<Data>.error(e);
}
});
How can I solve this problem?
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.
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.
}
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.
Can someone show me how to implement overriding flutter errors during widget test so I can check for my own custom errors.
I have seen snippets online mentioning this but all of my implementations fail
void main() {
testWidgets('throws an error when scanning unknown term types', (WidgetTester tester) async {
await tester.pumpWidget(injectTestWidget(new ItemScanScreen()));
await tester.enterText(find.byKey(new Key('term')), '');
await tester.tap(find.byIcon(Icons.send));
await tester.pump();
expect(
tester.takeException(),
isInstanceOf<UnrecognizedTermException>(),
reason: 'should have thrown an UnrecognizedTermException error but didn\'t',
);
});
}
the code above fails with the message below even though it looks like it in fact 'caught' my error:
The following UnrecognizedTermException was thrown running a test:
Instance of 'UnrecognizedTermException'
...
I read that you could do something like the snippet below but it did not see how/where to implement it:
final errorHandled = expectAsync0((){});
FlutterError.onError = (errorDetails) {
// handle error
errorHandled();
});
I use the code below in production to log errors to a server.
main.dart:
import 'dart:async';
import 'package:flutter/material.dart';
import 'logging.dart';
void main() async {
FlutterError.onError = (FlutterErrorDetails details) async {
new ErrorLogger().logError(details);
};
runZoned<Future<void>>(() async {
// Your App Here
runApp(MainApp());
}, onError: (error, stackTrace) {
new ErrorLogger().log(error, stackTrace);
});
}
logging.dart:
class ErrorLogger {
void logError(FlutterErrorDetails details) async {
//FlutterError.dumpErrorToConsole(details);
_sendToServer(details.exceptionAsString(), details.stack.toString());
}
void log(Object data, StackTrace stackTrace) async {
// print(data);
// print(stackTrace);
_sendToServer(data.toString(), stackTrace.toString());
}
void _sendToServer(String a, String b) async {
// Implementation here
}
}
This is was design for a test. I switched to wrapping logic in try/catch then running expect() on the "error message text" present concept. ex:
try {
throw new UnrecognizedTermException();
} catch (e) {
setState(() => _status = e.errMsg());
}
// then in my test
expect(find.text('Could not determine the type of item you scanned'), findsOneWidget);