Как продолжить выполнение последовательности Future, несмотря на неудачу?

traverse метод из Future объект останавливается при первом сбое. Я хочу толерантную / прощающую версию этого метода, которая при возникновении ошибок продолжается с остальной частью последовательности.

В настоящее время мы добавили следующий метод в наши утилиты:

def traverseFilteringErrors[A, B <: AnyRef]
                           (seq: Seq[A])
                           (f: A => Future[B]): Future[Seq[B]] = {
  val sentinelValue = null.asInstanceOf[B]
  val allResults = Future.traverse(seq) { x =>
    f(x) recover { case _ => sentinelValue }
  }
  val successfulResults = allResults map { result =>
    result.filterNot(_ == sentinelValue)
  }
  successfulResults
}

Есть лучший способ сделать это?

1 ответ

Решение

Действительно полезной вещью (вообще говоря) было бы возможность превратить ошибку будущего в правильную ценность. Или, другими словами, преобразовать Future[T] в Future[Try[T]] (успешное возвращаемое значение становится Success[T] в то время как случай отказа становится Failure[T]). Вот как мы можем это реализовать:

// Can also be done more concisely (but less efficiently) as:
// f.map(Success(_)).recover{ case t: Throwable => Failure( t ) }
// NOTE: you might also want to move this into an enrichment class
def mapValue[T]( f: Future[T] ): Future[Try[T]] = {
  val prom = Promise[Try[T]]()
  f onComplete prom.success
  prom.future
}

Теперь, если вы делаете следующее:

Future.traverse(seq)( f andThen mapValue )

Вы получите успешный Future[Seq[Try[A]]], чье конечное значение содержит Success экземпляр для каждого успешного будущего, и Failure экземпляр для каждого неудачного будущего. При необходимости вы можете использовать collect на этом последующем, чтобы бросить Failure экземпляры и сохраняют только удачные значения.

Другими словами, вы можете переписать ваш вспомогательный метод следующим образом:

def traverseFilteringErrors[A, B](seq: Seq[A])(f: A => Future[B]): Future[Seq[B]] = {
  Future.traverse( seq )( f andThen mapValue ) map ( _ collect{ case Success( x ) => x } )
}
Другие вопросы по тегам