2013-04-02 15 views
18

Metoda traverse od Future obiekt zatrzymuje się przy pierwszym uszkodzeniu. Chcę tolerancyjną/wybaczającą wersję tej metody, która po wystąpieniu błędów przenosi się z resztą sekwencji.Jak kontynuować wykonywanie sekwencji Przyszłości mimo niepowodzenia?

Obecnie dodaliśmy następujące metody do naszych utils:

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 
} 

Czy istnieje lepszy sposób to zrobić?

Odpowiedz

21

Prawdziwie użyteczną rzeczą (ogólnie rzecz biorąc) byłoby móc promować błąd przyszłości we właściwej wartości. Lub, innymi słowy, przekształć Future[T] w Future[Try[T]] (z powodzeniem zwracana jest wartość Success[T], a przypadek niepowodzenia staje się Failure[T]). Oto w jaki sposób możemy je zaimplementować:

// 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 
} 

Teraz, jeśli wykonaj następujące czynności:

Future.traverse(seq)(f andThen mapValue) 

Będziesz uzyskanie udanych Future[Seq[Try[A]]], którego wartość zawiera ewentualne wystąpienie Success dla każdej udanej przyszłości, i instancję Failure dla każdej nieudanej przyszłości. W razie potrzeby można następnie użyć collect w tym seq, aby usunąć instancje Failure i zachować tylko te wartości.

Innymi słowy, można przepisać metodę pomocniczą w następujący sposób:

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 }) 
} 
+2

super. Czy mógłbyś skomentować, dlaczego alternatywa ('f.map (Success ...') jest mniej wydajna? – sourcedelica

+3

Ponieważ 'map' oraz' recover' tworzą instancje 'Promise' i zwracają ich powiązaną' Future'. Instancje "obiecujące" zamiast jednej dla mojego alternatywnego rozwiązania, a "zwięzła" wersja rozpakowuje instancję 'Try' (przechowywaną wewnątrz instancji' Promise') tylko po to, aby ją ponownie zrewidować jako 'Success' lub' Failure 'instancja, która jest trochę marnotrawna Oczywiście musisz profilować, aby zobaczyć, czy faktycznie robi on jakąś znaczącą różnicę –

+0

Dzięki! To jest naprawdę pomocne – sourcedelica

Powiązane problemy