Как справиться с неудачным будущим в для понимания

#scala #future #optional #for-comprehension

#scala #будущее #тип опции #для понимания

Вопрос:

у меня есть следующее для понимания. Предполагается удалить строку в моей базе данных, но только если строка существует (так что, если есть новости для данного идентификатора):

 override def deleteNews(newsId: Long): Int = {
    val getAndDelete = for {
         Some(news) <- newsDao.get(newsId)// returns Future[Option[News]]
         delete <- newsDao.remove(news)   // returns Future[Int]
     } yield delete
     Await.result(getAndDelete, responseTimeout)
}
  

Но я не знаю, как справиться со случаем, когда для данного идентификатора нет элемента. В настоящее время генерируется это исключение:

 Unexpected exception[NoSuchElementException: Future.filter predicate is not satisfied]
  

Я надеюсь, что мой подход не ужасен: D

Я относительно новичок в scala.

Комментарии:

1. что означает возвращаемый тип Int ?

2. Строки, на которые влияет запрос в БД

3. Должен возвращаться Future[Into] и составлять оттуда, а не ждать

Ответ №1:

Использование Await не такая уж отличная идея: лучше всего отложить блокировку как можно дольше.

ИМО, ни один элемент для данного идентификатора не должен быть ошибкой. newsDao.get должен возвращать успешное будущее None , newsDao.remove если с этим идентификатором ничего нет, вы не должны вызывать deleteNews идентификатор, который не существует, если вы можете с этим помочь, и общий результат должен быть просто успешно удален нулевыми строками (поскольку я бы посмотрел на контракт newsId как на гарантию того, что в какой-то момент между вызовом и возвратом не было строк, связанных с,, (немного махнув рукой здесь вокруг гонки данных, конечно …)).

Итак, при условии, что вы не можете изменить newsDao реализацию:

 val getFut: Future[Option[News]] =
  newsDao.get(newsId).recover {
    // can still fail for other reasons
    case _: NoSuchElementException => None
  }

// I really prefer map/flatMap directly vs. for-comprehension sugar, especially when dealing with multiple monadicish things

// Not the most succinct, but leaving meaningful names in for documentation
val getAndRemove =
  getFut.flatMap { newsOpt =>
    newsOpt.map { news =>
      newsDao.remove(news)
    }.getOrElse(Future.successful(0))
  }
  

Если вам все еще нужно deleteNews возвращать голое Int значение, вы можете Await.result согласиться с тем, что иногда у вас будут возникать исключения и что это, вероятно, неоптимально.

Комментарии:

1. И да, я признаюсь, я не уверен, что смогу перевести это обратно в для понимания с первой попытки.

Ответ №2:

Как упоминал Леви, всегда старайтесь избегать блокировки, и когда вы сопоставляете шаблон, убедитесь, что вы обрабатываете все случаи.

Вы можете сделать это с помощью for-comprehension, как показано ниже:

 def deleteNews(newsId: Long): Future[Option[Int]] =
  for {
    news   <- newsDao.get(newsId)
    delete <- Future.sequence(news.map(id => newsDao.remove(id)).toList)
  } yield delete.headOption
  

Честно говоря, я не использовал этот трюк, чтобы перейти от Option[Future] к Future[Option] . Мне было бы интересно посмотреть, что говорят другие!