void main() {
foo().catchError((error) {
if (error is Future) {
error.then((value) => print('value = $value'));
}
});
}
Future<void> foo() async {
throw Future.error('FooError');
}
The error is caught inside catchError but I am not able to retrieve the value of Future.error which is FooError in this example.
Well that's Future.error so you'll have to again use catchError on it.
void main() {
foo().catchError((error) {
if (error is Future) {
error.catchError((error) => print(error)); // prints 'FooError'
}
});
}
Future<void> foo() async {
throw Future.error('FooError');
}
You just don't, there is no point in doing so.
You can either throw the error:
void main() {
foo().catchError((error) {
print('error = $error');
});
}
Future<void> foo() async {
throw 'FooError';
}
Or if that is for whatever reason not convinient, you can use Future.error to create a Future that will have an error already:
void main() {
foo().catchError((error) {
print('error = $error');
});
}
Future<void> foo() {
return Future.error('FooError');
}
But actually throwing a Future.error is redundant and not useful.
Related
If you run this in DartPad you'll never catch the exceptions, they are reported as unhandled in the console. Why?
import 'dart:html';
void main() async {
try {
final socket = await wsConnect('wss://127.0.0.1:7654');
print('${socket.readyState}');
} catch (e) {print(e);}
}
Future<WebSocket> wsConnect(String url) async {
final socket = WebSocket(url);
socket.onError.listen((_) => throw Exception('connection error'));
socket.onClose.listen((_) => throw Exception('connection close'));
return socket;
}
This doesn't work either:
import 'dart:html';
void main() async {
try {
final socket = await wsConnect('wss://127.0.0.1:7654', () => throw Exception('some problem'));
print('${socket.readyState}');
} catch (e) {print(e);}
}
Future<WebSocket> wsConnect(String url, void Function() onProblem) async {
final socket = WebSocket(url);
socket.onError.listen((_) => onProblem());
socket.onClose.listen((_) => onProblem());
return socket;
}
Are the stream callbacks being executed on some separate zone, isolate or VM or whatnot and where can I read about it?
I have the following function:
class CannotOpenMapException implements Exception {}
void launchMap(String address) async {
…
throw CannotOpenMapException();
}
And then in an onTap handler:
onTap: () {
try {
launchMap(my_address);
} on CannotOpenMapException {
print('caught exception!');
}
}
Thing is, the exception is not caught, the print statement is never executed.
I think the problem is with the way I start Crashlytics (though this is how the official docs recommend it):
void main() async {
runZonedGuarded<Future<void>>(() async {
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
…
runApp(const MyApp());
},
(error, stack) =>
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true));
}
Is there a way I can make Crashlytics only catch the exception I don't catch?
You should use the second argument "onError" of runZoneGuarder, this way only the exceptions that's you don't catch are intercepted.
runZonedGuarded<Future<void>>(() async {
WidgetsFlutterBinding.ensureInitialized();
... and stuff
runApp(const MyApp());
}, (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack);
});
The doc:
https://api.flutter.dev/flutter/dart-async/runZonedGuarded.html
I found the problem: The onTap handler was not async, so it just started launchMap and then exited. Now the launchMap execution was running "detached" (not sure that is the correct word for it). In order to catch it, onTap needs to be async and use the await keyword:
onTap: () async {
try {
await launchMap(widget.listing.address());
} on CannotOpenMapException {
…
}
})
onTap: () {
try {
await launchMap(my_address);
} on CannotOpenMapException {
print('caught exception!');
}
}
if you want to use async then use await.
main() async {
try {
final t = Test();
await Future.delayed(Duration(seconds: 1));
} catch (e) {
// Never printed
print("caught");
}
}
void willThrow() async {
throw "error";
}
class Test {
Test() {
willThrow();
}
}
If the "async" keyword is removed from willThrow everything works as expected.
Is it because you can't await a constructor? If so is there anyway to catch async errors in a constructor body?
Have this a go:
void main() async {
try {
final t = Test();
await Future.delayed(Duration(seconds: 1));
} catch (e) {
// Never printed
print("caught");
}
}
Future<void> willThrow() async {
throw "error";
}
class Test {
Test() {
willThrow().catchError((e){print('Error is caught here with msg: $e');});
}
}
As to the 'why':
You use a normal try/catch to catch the failures of awaited asynchronous computations. But since you cannot await the constructor, you have to register the callback that handles the exception in another way. I think :)
Since you never awaited the Future that was returned from willThrow(), and you never used the result of the Future, any exception thrown by the function is discarded.
There is no way to write an asynchronous constructor. So you are stuck with using old-school callbacks to handle errors, or simulate an async constructor with a static method:
void main() async {
try {
final t = await Test.create();
await Future.delayed(Duration(seconds: 1));
} catch (e) {
// Never printed
print("caught");
}
}
Future<void> willThrow() async {
throw "error";
}
class Test {
Test._syncCreate() {}
Future<void> _init() async {
await willThrow();
}
static Test create() async {
Test result = Test._syncCreate();
await result._init();
return result;
}
}
I upgraded Flutter from version 2.0.2 to version 2.2.2 and now the custom exceptions that are thrown from a Future function are not being catch.
For example, I got this Future function, where I call another Future that does a server request and returns back the response or throws a custom exception (ApiException) in case of error:
static Future<bool> signUpCustomerRequest(Map<String, dynamic> params) async {
try {
// Here we call this Future function that will do a request to server API.
dynamic _response = await _provider.signUpCustomer(params);
if (_response != null) {
updateUserData(_response);
return true;
}
return false;
} on ApiException catch(ae) {
// This custom exception is not being catch
ae.printDetails();
rethrow;
} catch(e) {
// This catch is working and the print below shows that e is Instance of 'ApiException'
print("ERROR signUpCustomerRequest: $e");
rethrow;
} finally {
}
}
And this is the Future function that does the request to server and throws the ApiException:
Future<User?> signUpCustomer(Map<String, dynamic> params) async {
// POST request to server
var _response = await _requestPOST(
needsAuth: false,
path: routes["signup_client"],
formData: params,
);
// Here we check the response...
var _rc = _response["rc"];
switch(_rc) {
case 0:
if (_response["data"] != null) {
User user = User.fromJson(_response["data"]["user"]);
return user;
}
return null;
default:
print("here default: $_rc");
// And here we have the throw of the custom exception (ApiException)
throw ApiException(getRCMessage(_rc), _rc);
}
}
Before upgrading to Flutter 2.2.2 the catch of custom exceptions worked perfectly. Did something change on this Flutter version? Am I doing something wrong?
Thanks!
I was able to reproduce your bug with the following code:
class ApiException implements Exception {
void printDetails() {
print("ApiException was caught");
}
}
Future<void> doSomething() async {
await Future.delayed(Duration(seconds: 1));
throw ApiException();
}
void main() async {
try {
await doSomething();
} on ApiException catch (ae) {
ae.printDetails();
} catch (e) {
print("Uncaught error: $e"); // This line is printed
}
}
There's an open issue on the dart sdk, which I think might be related, though I'm not sure: https://github.com/dart-lang/sdk/issues/45952.
In any case, I was able to correct the error by returning a Future.error, instead of throwing the error directly:
class ApiException implements Exception {
void printDetails() {
print("ApiException was caught"); // This line is printed
}
}
Future<void> doSomething() async {
await Future.delayed(Duration(seconds: 1));
return Future.error(ApiException());
}
void main() async {
try {
await doSomething();
} on ApiException catch (ae) {
ae.printDetails();
} catch (e) {
print("Uncaught error: $e");
}
}
I'd like to have common try/catch/finally logic in a decorator-like feature that can "wrap" a function or class method. Consider the scenario:
Class MyClass {
void someMethodA() {
doSomeInitialWork();
try {
doSomething();
} catch (err) {
throw err;
} finally {
doSomeCleanUpWork();
}
}
void someMethodB() {
doSomeInitialWork();
try {
doSomethingElse();
} catch (err) {
throw err;
} finally {
doSomeCleanUpWork();
}
}
}
So on and so forth. The unique parts of each method are just the try body. If I have a bunch of methods, some which require the same logic, is there a "nice" way to avoid redundant code?
Ideally it could be syntax like:
#wrapper
void someMethodA() {
doSomething();
}
#wrapper
void someMethodB() {
doSomethingElse();
}
MyClassInstance.someMethodA(); // call it like this and the wrapper takes care of everything
but I know those are annotations in Dart and not applicable here.
UPDATE
Following jamesdlin answer, I am trying to incorporate the anonymous function solution to a futures/async/await scenario:
Future<dynamic> trySomething(Future<dynamic> Function() callback) async {
doSomeInitialWork();
try {
return await callback();
} catch (err) {
throw err;
} finally {
doSomeCleanUpWork();
}
}
class MyClass {
Future<List<String>> someMethodA() async {
return await trySomething(() async {
return await someApiCall();
});
}
}
That seems to work, but it looks kind of messy. I'm not sure if what I'm doing in the async/await example is appropriate.
Anonymous functions in Dart are rather common (unlike Python, where lambda is very restricted).
You therefore could make a helper function that takes the unique part as a callback.
void trySomething(void Function() body) {
doSomeInitialWork();
try {
body();
} catch (err) {
throw err;
} finally {
doSomeCleanUpWork();
}
}
void someMethodA() {
trySomething(() {
doSomething();
});
}
void someMethodB() {
trySomething(() {
doSomethingElse();
});
}
That's basically what test() from package:test (or testWidgets() from Flutter) do.
Update for the case described in the comment: It's not much different if the methods return Futures. For example, if you start with:
Future<List<String>> someMethodA() async {
return await blah();
}
then you could do:
Future<R> trySomethingAsync<R>(Future<R> Function() body) async {
doSomeInitialWork();
try {
return await body();
} catch (err) {
throw err;
} finally {
doSomeCleanUpWork();
}
}
Future<List<String>> someMethodA() {
return trySomethingAsync(() async {
return await blah();
});
}