I came across a behaviour I am unable to work around. Basically, I am trying to fetch data, and return a Future
of these data, where some of the requests can fail but we can ignore them.
This is my idea so far : for each data we want to get, create a future that returns null if it fails, and the value otherwise. Then wait all the futures, and remove null values.
Here is my code : (simple test data)
Future<List<String>> testFunc() {
// create a result list
List<Future<String?>> result = List.empty(growable: true);
// put 10 future in the results
for(int i = 0; i < 10; i++) {
result.add(
// create a future that may fail
Future.delayed(Duration(seconds: i), () {
if(i % 2 == 0) { return i; }
else { throw Error(); }
})
// if the future succeed, return the value
.then((value) {
return "got from future : $value";
})
// otherwise, returns "null" as we are expecting a future returning a nullable value
.catchError((error) {
return null; // <========= Linter error here
})
);
}
// then, wait for all futures, and remove the ones that crashed
return Future.wait(result).then((listOfNullable) => listOfNullable.where((element) => element != null).map((e) => e!).toList());
}
When I run this and one of the futures fails and return null, I’m having an error :
The error handler of Future.catchError must return a value of the future's type
which I don’t understand, as null is a valid value for String?
?
Something that does work is with explicit casts :
Future<List<String>> testFunc() {
// create a result list
List<Future<String?>> result = List.empty(growable: true);
// put 10 future in the results
for(int i = 0; i < 10; i++) {
result.add(
// create a future that may fail
Future.delayed(Duration(seconds: i), () {
if(i % 2 == 0) { return i; }
else { throw Error(); }
})
// if the future succeed, return the value
.then((value) {
return "got from future : $value" as String?; // <========= Linter error here
})
// otherwise, returns "null" as we are expecting a future returning a nullable value
.catchError((error) {
return null as String?; // <========= Linter error here
})
);
}
// then, wait for all futures, and remove the ones that crashed
return Future.wait(result).then((listOfNullable) => listOfNullable.where((element) => element != null).map((e) => e!).toList());
}
But now, the linter tells me I have an unnecessary cast, and I’m trying to remove any linter errors.
What I am missing here ?
2
Answers
The linter errors are caused by the call to
then(...)
, which the dart linter eagerly resolves tothen<String>
instead ofthen<String?>
.You can specify the type explicitly to work around this behavior:
The
Future.catchError
callback must return the same type as the originalFuture
. That is, the callback you supply toFuture<T>.catchError
must return either aT
or aFuture<T>
(also known asFutureOr<T>
).Since you call
catchError
on theFuture<R>
returned byFuture.then<R>
, the error callback must return aFutureOr<R>
. You do:Since you don’t provide an explicit type for
Future.then<R>
,R
is inferred from the return type of callback, which is a non-nullableString
. As jraufeisen explained, you therefore can fix this by explicitly specifyingR
instead of allowing it to be inferred.Alternatively, I strongly recommend using
async
–await
withtry
–catch
instead of usingFuture.then
andFuture.catchError
. Doing so avoids these sorts of problems (or at least makes them much easier to reason about). In your case, a helper function would make that transformation easier:I’d also use
.whereType
instead of.where
with.map
to filter out thenull
s, use collection-for
to avoid creating an emptyList
and growing it, and remove the remaining use ofFuture.then
: