Линтер говорит мне, что каст не нужен, но без него вылетает

Я столкнулся с поведением, с которым не могу справиться. По сути, я пытаюсь получить данные и вернуть 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());
}

Но теперь линтер сообщает мне, что у меня есть ненужное приведение, и я пытаюсь удалить все ошибки линтера.

Что мне здесь не хватает?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
50
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Ошибки линтера вызваны вызовом 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();
}

Другие вопросы по теме