Description
(Forked from an internal thread; @munificent and others have additional context)
I noticed in code reviews that developers often do:
return someFuture.catchError((e) => log(...));
But this pattern actually swallows the error, and instead propagates a null to the caller, which may cause an NPE or worse down the future chain. This pattern is only good if catchError returned future is a terminal (="orphaned") future (which is often an error-prone pattern too).
@munificent writes, in response:
In most cases, the easy fix is to avoid using catchError() at all and use async/await instead. That way, you can use a regular catch clause where people (hopefully!) already know how to log and rethrow exceptions correctly.
Compare with Java Futures, which doesn't have his errorprone pattern, because its catching function takes a Function<?, V>, not Function, and in Java, and the void output of print would not match with V.
We can't do that in Dart because catchError() accepts either of two kinds of functions: a single-parameter function that just accepts the error, or a two-parameter one that accepts the error and stack trace. We don't have overloading in Dart, so we have to loosen catchError()'s parameter type to Function to accept both kinds.
(Java, of course, doesn't have this problem because it puts the stack trace directly on the exception object instead of passing it around separately.)
You can see a minimal example of this problem in action in the following code sample:
void main() {
invokeLazy(() => aVoidMethod());
}
// We need this to actually return a String, but we can't specify the type due to lack of overloads.
// See https://api.dartlang.org/stable/2.0.0/dart-async/Future/catchError.html.
void invokeLazy(Function aString) {
print(aString);
}
void aVoidMethod() {}
API reference for <Future>.catchError
:
https://api.dartlang.org/stable/2.0.0/dart-async/Future/catchError.html
EDIT: I believe there is another bug, somewhere, about the hidden errors you get if you write:
// (ArgumentError) => void is not a (dynamic) => dynamic.
future.catchError((ArgumentError e) => ...);