Современные фьючерсы на scala похожи на Either
в том смысле, что содержат либо успешный результат, либо Throwable
. Если вы повторно посетите этот код в Scala 2.10, я думаю, вы найдете ситуацию довольно приятной.
В частности, scala.concurrent.Future [T] технически только "is-a" Awaitable[T]
, но _.onComplete
и Await.ready(_, timeout).value.get
оба представляют свой результат как scala.util . Попробуйте [T] , что очень похоже на Either[Throwable, T]
в том смысле, что это либо результат, либо исключение.
Как ни странно, _.transform
принимает две функции отображения, одну для T => U
и одну для Throwable => Throwable
, и (если я что-то упустил) нет преобразователя, который отображал бы будущее как Try[T] => Try[U]
. Future
.map
позволит вам превратить успех в неудачу, просто выбрасывая исключение в функции отображения, но оно использует его только для успехов исходного Future
. Его .recover
также может превратить неудачу в успех. Если вы хотите иметь возможность изменять успехи на неудачи и наоборот, вам нужно было бы создать что-то самостоятельно, представляющее собой комбинацию _.map
и _.recover
, или же использовать _.onComplete
, чтобы связать с новым scala.concurrent.Promise [U] примерно так:
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
}
, который будет использоваться так:
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()
, что даст что-то вроде этого:
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
(или вы можете "pimp" Future
в RichFutureWithFlexibleTransform
с неявным определением и сделать flexibleTransform
функцией-членом этого, удалив параметр fut
и просто используя this
)
(еще лучше было бы взять Try[T] => Future[U]
и назвать его flexibleFlatMap
, чтобы вы могли выполнять асинхронные операции в преобразовании)