Failing a Future based on a successful result
Asked Answered
M

3

6

I have a scenario in my code where I need to make a Future fail based on a successful result that contains a certain value. I can make this work just fine via flatMap, but I want to know if there is a cleaner way to make this work. First, a very simplified example:

import concurrent._

case class Result(successful:Boolean)
object FutureTest {
  def main(args: Array[String]) {
    import ExecutionContext.Implicits._

    val f = Future{Result(false)}.flatMap{ result =>
      result match{
        case Result(false) => Promise.failed(new Exception("The call failed!!")).future
        case _ => Promise.successful(result).future
      }
    }

    f onFailure{
      case x => println(x.getMessage())
    }
  }
}

So in my example here, I want the Future to be failed if the Result returned has a value of false for its success indicator. As I mentioned, I can make this work okay with flatMap, but the line of code I would like to eliminate is:

case _ => Promise.successful(result).future

This statement seems unnecessary. The behavior that I would like is to be able to define the condition and if it evaluates to true, allow me to return a different Future as I am doing, but if it's not true, just leave things as they are (sort of like PartialFunction semantics. Is there a way to do this that I'm just not seeing? I've looked at collect and transform and those don't seem to be the right fit either.

Edit

After getting the map suggestion from @Rex Kerr and @senia, I created a PimpedFuture and an implicit conversion to pretty up the code a bit like so:

class PimpedFuture[T](f:Future[T])(implicit ex:ExecutionContext){
  def failWhen(pf:PartialFunction[T,Throwable]):Future[T] = {
    f map{
      case x if (pf.isDefinedAt(x)) => throw pf(x)
      case x => x
    }
  }
}

The implicit

  implicit def futToPimpedFut[T](fut:Future[T])(implicit ec:ExecutionContext):PimpedFuture[T] = new PimpedFuture(fut)

And the new handling code:

val f = Future{ Result(false) } failWhen {
  case Result(false) => new Exception("The call failed!!")
}

I think this is a little cleaner while still utilizing the suggestion to use map.

Mis answered 22/5, 2013 at 14:56 Comment(5)
You want new future in both cases? Or only in case of failure?Contactor
The result itself, in my actual code, is being returned from a call to an actor. I don't want that actor to always propagate a failure upstream given this kind of result. It's based on successfully storing a value into couchbase via a call to add a key. No all calling code cares that the value of the success flag is false; it's situational.Mis
Sounds like a filter method.Azimuth
@om-nom-nom, I only want a new future if my match condition is met. If match is not met, leave the existing future alone.Mis
@senia, I looked at filter. The issue there is that if my predicate is not true then the resulting Future is failed with a NoSuchElementException and that is undesired.Mis
O
8

You can do this with less effort with map:

val f = Future{ Result(false) } map {
  case Result(false) => throw new Exception("The call failed!!")
  case x => x
}

but you still need to explicitly mention that you're passing the identity through.

Note that that you should not use andThen in such cases. andThen is for side effects, not for changing the result (unlike the Function1 method of the same name).

Openmouthed answered 22/5, 2013 at 15:8 Comment(8)
You should use map, not andThen.Azimuth
This would work if only it didn't throw the exception all the way out and not then get to my onFailure callback. When I run this, I get a stacktrace in the code execution, so that's not the desired effect.Mis
I'm confused as to how map will work. The map function will go from a successful result of type A to a successful result of type B. I want to fail the future given a certain condition and that's why I had been using flatMap. I'll be interested in seeing how map applies here.Mis
@cmbaxter: In this case B and A is the same type. The result of throw is Nothing and Nothing is subtype of all types, so it's also subtype of A.Azimuth
I don't love it, but I think it will work. Now, who is actually responsible for this answer so I can credit the right person. Rex Kerr submitted it but @Azimuth suggested it and edited it.Mis
@cmbaxter: There is only 1 answer here. SO is a wiki-resource, not a MMORPG.Azimuth
@Azimuth - I think it would have been cleaner to delete mine and add yours, but this works too.Openmouthed
@andreyne - Indeed. Fixed.Openmouthed
D
0

There is also a different solution (without stacktrace side effect as @cmbaxter mentioned)

val prom = Promise[Int]()
val f = future {
    1
}

f onComplete {
    case Success(i) if i < 5 => prom.failure(new RuntimeException("_ < 5"))
    case Success(x) => prom.success(x)
}

prom.future onComplete {
    case Success(s) => println(s"We cool '$s'")
    case Failure(th) => println(s"All failed, ${th.getMessage}")
}
Depression answered 1/7, 2014 at 11:15 Comment(0)
A
0

The cleanest solution is to call the filter method, as @senia mentioned in the comments.

Future { Result(false) }.filter(_.successful) // throws NoSuchElementException

If you want a different exception type, you can chain the call with recoverWith:

Future { Result(false) }.filter(_.successful).recoverWith {
  case _: NoSuchElementException => Future.failed(new Exception("The call failed!!"))
}
Anchorage answered 13/3, 2019 at 18:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.