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

Метод 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

По-настоящему полезная вещь (вообще говоря) должна была бы способствовать тому, чтобы ошибка будущего в правильное значение. Другими словами, преобразуйте a 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 в этом seq, чтобы отбросить экземпляры 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 } )
}