31

I have a Future[T] and I want to map the result, on both success and failure.

Eg, something like

val future = ... // Future[T]
val mapped = future.mapAll { 
  case Success(a) => "OK"
  case Failure(e) => "KO"
}

If I use map or flatmap, it will only map successes futures. If I use recover, it will only map failed futures. onComplete executes a callback but does not return a modified future. Transform will work, but takes 2 functions rather than a partial function, so is a bit uglier.

I know I could make a new Promise, and complete that with onComplete or onSuccess/onFailure, but I was hoping there was something I was missing that would allow me to do the above with a single PF.

Mario Galic
  • 47,285
  • 6
  • 56
  • 98
sksamuel
  • 16,154
  • 8
  • 60
  • 108

4 Answers4

46

Edit 2017-09-18: As of Scala 2.12, there is a transform method that takes a Try[T] => Try[S]. So you can write

val future = ... // Future[T]
val mapped = future.transform {
  case Success(_) => Success("OK")
  case Failure(_) => Success("KO")
}

For 2.11.x, the below still applies:

AFAIK, you can't do this directly with a single PF. And transform transforms Throwable => Throwable, so that won't help you either. The closest you can get out of the box:

val mapped: Future[String] = future.map(_ => "OK").recover{case _ => "KO"}

That said, implementing your mapAll is trivial:

implicit class RichFuture[T](f: Future[T]) {
  def mapAll[U](pf: PartialFunction[Try[T], U]): Future[U] = {
    val p = Promise[U]()
    f.onComplete(r => p.complete(Try(pf(r))))
    p.future
  }
}
polo
  • 1,352
  • 2
  • 16
  • 35
espenhw
  • 871
  • 8
  • 8
  • 3
    Yeah I could easily write it, just wondered if something like that was lurking about somewhere. – sksamuel Apr 13 '14 at 14:39
  • 2
    Hi @espenhw, in the 2nd paragraph in your answer, did you actually mean `val mapped: Future[String] = future.map(_ => "OK").recover{case _ => "KO")` – ZJ Lyu Jan 09 '17 at 07:13
  • It was driving me crazy not figuring out which combinator to use - especially since .onComplete has exactly the signature I want, except it doesn't give me a new future. I'll use this solution; thanks. – kornfridge Feb 02 '17 at 15:30
  • the thing is the .map could fail and you'd recover it erroneously (in the sense we only wanted to recover from things in the initial future). – Andy Hayden Sep 05 '17 at 22:24
  • Can we update this answer with the new `transform` method introduced as part of Scala 2.12? – saheb Sep 18 '17 at 13:41
7

Since Scala 2.12 you can use transform to map both cases:

future.transform {
      case Success(_) => Try("OK")
      case Failure(_) => Try("KO")
}

You also have transformWith if you prefer to use a Future instead of a Try. Check the documentation for details.

nmat
  • 7,430
  • 6
  • 30
  • 43
4

In a first step, you could do something like:

import scala.util.{Try,Success,Failure}

val g = future.map( Success(_):Try[T] ).recover{
  case t => Failure(t)
}.map {
  case Success(s) => ...
  case Failure(t) => ...
}

where T is the type of the future result. Then you can use an implicit conversion to add this structure the Future trait as a new method:

implicit class MyRichFuture[T]( fut: Future[T] ) {
  def mapAll[U]( f: PartialFunction[Try[T],U] )( implicit ec: ExecutionContext ): Future[U] = 
    fut.map( Success(_):Try[T] ).recover{
      case t => Failure(t)
    }.map( f )
 }

which implements the syntax your are looking for:

val future = Future{ 2 / 0 }
future.mapAll {
  case Success(i) => i + 0.5
  case Failure(_) => 0.0
}
paradigmatic
  • 40,153
  • 18
  • 88
  • 147
4

Both map and flatMap variants:

implicit class FutureExtensions[T](f: Future[T]) {
  def mapAll[Target](m: Try[T] => Target)(implicit ec: ExecutionContext): Future[Target] = {
    val promise = Promise[Target]()
    f.onComplete { r => promise success m(r) }(ec)
    promise.future
  }

  def flatMapAll[Target](m: Try[T] => Future[Target])(implicit ec: ExecutionContext): Future[Target] = {
    val promise = Promise[Target]()
    f.onComplete { r => m(r).onComplete { z => promise complete z }(ec) }(ec)
    promise.future
  }
}
Anton Malmygin
  • 3,406
  • 2
  • 25
  • 31
sungiant
  • 3,152
  • 5
  • 32
  • 49