consider this code:
EXAMPLE A:
Future<void> fakeCall() async {
await Future.delayed(const Duration(milliseconds: 300), () {
throw MyError('myException');
});
}
Future<void> fetch() async {
Future<void> testFuture = fakeCall();
testFuture.whenComplete(() {
print('when completed');
});
testFuture.catchError((e) {
print('on error');
});
return testFuture;
}
void main() async {
try{
await fetch();
}on MyError catch(e){
//getting here but one still leaks!!
}
}
when calling await fetch() I expect only catchError to be called. but the Error somehow leaks, so basically two were thrown...one caught by catchError and one is leaked outside as an unhandled error.
but when using it the proposed way (chained one after the other):
EXAMPLE B:
Future<void> fakeCall() async {
await Future.delayed(const Duration(milliseconds: 300), () {
throw MyError('myException');
});
}
Future<void> fetch() async {
Future<void> testFuture = fakeCall().whenComplete(() {
print('when completed');
}).catchError((e) {
print('on error');
});
return testFuture;
}
void main() async {
try{
await fetch();
}on MyError catch(e){
//getting here and nothing leaks!!
}
}
No error leaking occurs. It just left me very curious, can someone explain why writing it in the first way makes the error to leak?
update
originally I forgot to add try/catch around the fetch function, so you might think the error leaked for this reason. but notice, the error still leaks when not chaining the one rror properly (first example)
CodePudding user response:
The error is leaked because you are awaiting the result on a Future which ends up being completed with an error in:
await fetch();
It is important to understand that a Future object is just a value which gets a value sometimes in the future and this value can either be a value or an error.
The catchError() is a way to subscribe some code to be executed if the Future gets completed as an error. The method returns a new future with the logic that it gets the origin value in case of the origin future gets completed without any error. But if the origin gets completed with an error, the catchError() runs a specified method and returns the value after this execution.
The catchError() does not change anything about the origin Future and you can therefore call catchError() as many times you want.
I should add that I think it is better to use a traditional try...catch (combined with await) inside async-marked functions instead of playing with catchError() since it makes the code more readable and less confusing:
Future<void> fetch() async {
try {
await fakeCall();
} catch (e) {
print('on error');
} finally {
print('when completed');
}
}
void main() async {
await fetch();
}
Outputs:
on error
when completed
Update after question was changed
The problem with "Example B" is that you assume that whenComplete() does handle any errors. But if you read the the documentation:
The future returned by this call,
f, will complete the same way as this future unless an error occurs in theactioncall, or in a Future returned by theactioncall. If the call toactiondoes not return a future, its return value is ignored.
https://api.dart.dev/stable/2.15.1/dart-async/Future/whenComplete.html
So the returned Future have not changed its behavior so if the origin Future completes with an error, the same is happening with the Future created from whenComplete(). And since you are not attaching any error handling on this Future, your program fails.
