futures modernes scala sont comme Either
en ce qu'elles contiennent soit un résultat positif ou un Throwable
. Si vous visitez à nouveau ce code dans Scala 2.10, je pense que vous trouverez la situation très agréable.
Plus précisément, sur le plan technique que scala.concurrent.Future[T] « est-un » Awaitable[T]
, mais _.onComplete
et Await.ready(_, timeout).value.get
présentent tous deux le résultat en tant que scala.util.Try[T], ce qui est beaucoup comme Either[Throwable, T]
en ce qu'il est soit le résultat ou une exception.
Bizarrement, _.transform
prend deux fonctions de cartographie, un pour T => U
et un pour Throwable => Throwable
et (à moins que je me manque quelque chose) il n'y a pas de transformateur qui associe l'avenir Try[T] => Try[U]
. Future
.map
vous permettra de transformer un succès en échec en lançant simplement une exception dans la fonction de mappage, mais il l'utilise uniquement pour les succès de l'original Future
. Son .recover
, de même peut transformer un échec en succès. Si vous voulez être en mesure de changer succès à des échecs et vice-versa, vous aurez besoin de construire quelque chose qui vous était une combinaison de _.map
et _.recover
ou utiliser d'autre _.onComplete
à la chaîne à une nouvelle scala.concurrent.Promise[U] comme ceci:
import scala.util.{Try, Success, Failure}
import scala.concurrent.{Future, Promise}
import scala.concurrent.ExecutionContext
def flexibleTransform[T,U](fut: Future[T])(f: Try[T] => Try[U])(implicit ec: ExecutionContext): Future[U] = {
val p = Promise[U]
fut.onComplete { res =>
val transformed = f(res)
p.complete(transformed)
}
p.future
}
qui serait utilisé comme ceci:
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Await
import scala.concurrent.duration.Duration.Inf
def doIt() {
val a: Future[Integer] = Future {
val r = scala.util.Random.nextInt
if (r % 2 == 0) {
throw new Exception("we don't like even numbers")
} else if (r % 3 == 0) {
throw new Exception("we don't like multiples of three")
} else {
r
}
}
val b: Future[String] = flexibleTransform(a) {
case Success(i) =>
if (i < 0) {
// turn negative successes into failures
Failure(new Exception("we don't like negative numbers"))
} else {
Success(i.toString)
}
case Failure(ex) =>
if (ex.getMessage.contains("three")) {
// nevermind about multiples of three being a problem; just make them all a word.
Success("three")
} else {
Failure(ex)
}
}
val msg = try {
"success: " + Await.result(b, Inf)
} catch {
case t: Throwable =>
"failure: " + t
}
println(msg)
}
for { _ <- 1 to 10 } doIt()
qui donnerait quelque chose comme ceci:
failure: java.lang.Exception: we don't like even numbers
failure: java.lang.Exception: we don't like negative numbers
failure: java.lang.Exception: we don't like negative numbers
success: three
success: 1756800103
failure: java.lang.Exception: we don't like even numbers
success: 1869926843
success: three
failure: java.lang.Exception: we don't like even numbers
success: three
(ou vous pourriez « proxénète » Future
dans un RichFutureWithFlexibleTransform
avec une implicite def et faire flexibleTransform
une fonction membre de cela, laissant tomber le fut
param et simplement en utilisant this
)
(encore mieux serait de prendre Try[T] => Future[U]
et l'appeler flexibleFlatMap
donc vous pouvez faire des choses asynchrones dans la transformation)
Oui, utilisez l'une ou l'autre, ou mieux, Validation à partir de scalaz. –
Thx, jetterai un oeil aux deux suggestions :) – rocksteady