Scala Future with filter in for comprehension

后端 未结 4 642
春和景丽
春和景丽 2021-02-04 00:45

In the example below I get the exception java.util.NoSuchElementException: Future.filter predicate is not satisfied

I want to have the result Future(

相关标签:
4条回答
  • 2021-02-04 01:05

    Of course I figured out one solution myself. Perhaps there are better, more idiomatic, solutions?

    import scala.concurrent.Future
    import scala.util.{ Try, Success, Failure }
    import scala.concurrent.ExecutionContext.Implicits.global
    
    val f1 = Future( 1 )
    val f2 = for {
      i <- f1
      if( i == 2 )
    } yield "Test1"
    val f3 = f2.recover{ case _ => "Test2"  }
    // OR val f3 = f2.fallbackTo( Future( "Test2" ) )
    f3.value
    
    0 讨论(0)
  • 2021-02-04 01:08

    This is a more idiomatic solution, in my opinion. This predicate function creates either a Future[Unit] or a failed future containing your exception. For your example, this would result in either a Success("Test1") or a Failure(Exception("Test2")). This is slightly different from "Test1" and "Test2", but I find this syntax to be more useful.

    def predicate(condition: Boolean)(fail: Exception): Future[Unit] = 
        if (condition) Future( () ) else Future.failed(fail)
    

    You use it like this:

    val f2 = for {
      i <- f1
      _ <- predicate( i == 2 )(new Exception("Test2"))
      j <- f3  // f3 will only run if the predicate is true
    } yield "Test1"
    
    0 讨论(0)
  • 2021-02-04 01:21

    In your for-comprehension, you are filtering by i == 2. Because the value of f1 is not two, it will not yield a Success but instead a Failure. The predicate of the filter is not satisfied, as your errror message tells you. However, f2.recover returns a new Future. The value of f2 is not manipulated. It still stores the Failure. That is the reason you get the error message when you call f2.value.

    The only alternative I can think of would be using an else in your for-comprehension as shown here.

    val f2 = for ( i <- f1) yield {
      if (i == 2) "Test1"
      else "Test2"
    }
    f2.value
    

    This will return Some(Success(Test2)) as your f3.value does.

    0 讨论(0)
  • 2021-02-04 01:21

    I liked @pkinsky 's idea, and made a bit of improvement. I dropped code to create Exception object like this:

    val f2 = for {
      i <- f1
      _ <- shouldTrue( i == 2 )
      j <- f3  // f3 will only run if the predicate is true
    } yield "Test1"
    

    shouldTrue function is implemented using lihaoyi`s sourcecode library:

    def shouldTrue(condition: sourcecode.Text[Boolean])(implicit enclosing: sourcecode.Enclosing, file: sourcecode.File, line: sourcecode.Line): Future[Unit] =
      if (condition.value) Future.successful( () ) else Future.failed(new Exception(s"${condition.source} returns false\n\tat ${file.value}:${line.value}"))
    

    Then it automatically generates more meaningful exception message:

    java.lang.Exception: i == 2 returns false
        at \path\to\example.scala:17
    
    0 讨论(0)
提交回复
热议问题