Я столкнулся с поведением, с которым не могу справиться. По сути, я пытаюсь получить данные и вернуть Future
этих данных, где некоторые запросы могут завершиться ошибкой, но мы можем их игнорировать.
Это моя идея на данный момент: для каждых данных, которые мы хотим получить, создайте будущее, которое возвращает null, если оно терпит неудачу, и значение в противном случае. Затем подождите все фьючерсы и удалите нулевые значения.
Вот мой код: (простые тестовые данные)
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());
}
Когда я запускаю это, и один из фьючерсов терпит неудачу и возвращает ноль, у меня возникает ошибка:
The error handler of Future.catchError must return a value of the future's type
чего я не понимаю, так как null является допустимым значением для String?
?
Что-то, что действительно работает, это с явными приведениями:
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());
}
Но теперь линтер сообщает мне, что у меня есть ненужное приведение, и я пытаюсь удалить все ошибки линтера.
Что мне здесь не хватает?
Ошибки линтера вызваны вызовом then(...)
, который дротик-линтер охотно разрешает в then<String>
вместо then<String?>
.
Вы можете явно указать тип, чтобы обойти это поведение:
Future<List<String>> testFunc() {
List<Future<String?>> result = List.empty(growable: true);
for(int i = 0; i < 10; i++) {
result.add(
Future.delayed(Duration(seconds: i), () {
if (i % 2 == 0) { return i; }
else { throw Error(); }
})
.then<String?>((value) { // <- Change here!
return "got from future : $value";
})
.catchError((error) {
return null; // No more linter warning
})
);
}
// 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());
}
Обратный вызов Future.catchError должен возвращать тот же тип, что и исходный Future. То есть обратный вызов, который вы предоставляете Future<T>.catchError
, должен возвращать либо T
, либо Future<T>
(также известный как FutureOr<T>
).
Поскольку вы вызываете catchError
для Future<R>
, возвращаемого Future.then<R>
, обратный вызов ошибки должен возвращать FutureOr<R>
. Вы делаете:
.then((value) {
return "got from future : $value";
})
Поскольку вы не указываете явный тип для Future.then<R>
, R
выводится из возвращаемого типа обратного вызова, который не может быть нулевым String
. Как объяснил jraufeisen, вы можете исправить это, явно указав R
вместо того, чтобы позволять делать вывод.
В качестве альтернативы я настоятельно рекомендую использовать async
-await
с try
-catch
вместо использования Future.then
и Future.catchError
. Это позволяет избежать такого рода проблем (или, по крайней мере, сделать их более понятными). В вашем случае вспомогательная функция упростит это преобразование:
Future<List<String>> testFunc() {
// create a result list
List<Future<String?>> result = List.empty(growable: true);
Future<String?> helper(int i) async {
try {
// create a future that may fail
var value = await Future.delayed(Duration(seconds: i), () {
if (i % 2 == 0) {
return i;
} else {
throw Error();
}
});
// if the future succeed, return the value
return "got from future : $value";
} on Error {
// otherwise, returns "null" as we are expecting a future returning a nullable value
return null;
}
}
// put 10 future in the results
for (int i = 0; i < 10; i++) {
result.add(helper(i));
}
// 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());
}
Я бы также использовал .whereType вместо .where
с .map
, чтобы отфильтровать null
, использовать collection-for
, чтобы избежать создания пустого List
и его увеличения, и удалить оставшееся использование Future.then
:
Future<List<String>> testFunc() async {
Future<String?> helper(int i) async {
try {
// create a future that may fail
var value = await Future.delayed(Duration(seconds: i), () {
if (i % 2 == 0) {
return i;
} else {
throw Error();
}
});
// if the future succeed, return the value
return "got from future : $value";
} on Error {
// otherwise, returns "null" as we are expecting a future returning a nullable value
return null;
}
}
// then, wait for all futures, and remove the ones that crashed
var result = await Future.wait([
for (var i = 0; i < 10; i++) helper(i),
]);
return result.whereType<String>().toList();
}