当前位置: 动力学知识库 > 问答 > 编程问答 >

Scala Futures callback hell

问题描述:

I've been reading many times about Scala Futures reducing callback problems. I've got a code that started to look problematic.

val a = Future(Option(Future(Option(10))))

a.map { b =>

b.map { c =>

c.map { d =>

d.map { res =>

res + 10

}

}

}

}

How can I make this code more flat?

//Edit @againstmethod

for{

b <- a

c <- b

d <- c

res <- d

} yield res + 10

This code won't compile

Error:(21, 8) type mismatch; found : Option[Int] required:

scala.concurrent.Future[?] res <- d

^

网友答案:

You can use a for comprehension. In example:

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

object Stuff extends App {
  val result = for {
    f1 <- Future { 10 + 1 }
    f2 <- Future { f1 + 2 }
  } yield f2
  result.onComplete(println)
}

Where result will be 13.

Any class that implements a proper map and flatMap function can be used this way in a for.

If you don't mind another dependency, you can also use a library like scalaz and explicitly use monadic binding to flatten things out (EDIT encoded some Option types to address a comment below):

import scalaz._
import Scalaz._
import scala.concurrent._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.util.{Success,Failure}

object BindEx extends App {

  def f1(i: String): Future[Int] = Future { i.length }
  def f2(i: Int): Future[Option[Double]] = Future { Some(i / Math.PI) }
  def f3(i: Option[Double]): Future[Option[Double]] = Future { 
    i match {
      case Some(v) => Some(Math.round(v))
      case _ => None
    } 
  }

  val result = 
    Monad[Future].point("Starting Point") >>= 
    f1 >>= 
    f2 >>=
    f3

  result.onComplete { x => 
    x match {
      case Success(value) => println("Success " + value)
      case Failure(ex) => println(ex)
    }  
  }

  Await.result(result, 1 seconds)
}

And finally, if you just have parallel operations that you want to bind after all have succeeded that are independent, you can use scalaz applicative builder:

  val result = (
    Future { 10 + 10 } |@| 
    Future { 20 - 3 } |@| 
    Future { Math.PI * 15 }
  ) { _ + _ / _}
  println(Await.result(result, 1 seconds))

This will let all 3 futures complete, then apply block to the 3 arguments.

网友答案:

Actually the answer was pretty straight forward.

for { 
a <- b
c <- a.get
} yield c.get + 10

Appears to be enough, because when x.get + 10 fails (because of None + 10) the future just fails. So it still works to use a simple fallback

val f = for { 
a <- b
c <- a.get
} yield c.get + 10
f fallbackTo Future.successful(0)
网友答案:

I haven't used them yet, but they should be exactly what you're looking for: Monad transformers.

Basically, a monad transformer takes a Monad (such as Future) and adds functionality to it, such as the functionality provided by Option, and returns a transformed Monad. I think there is even an Option Monad transformer in Scalaz. That should allow you to use nested Options in Futures and still have a flat code structure using for comprehensions.

See http://blog.garillot.net/post/91731853561/a-question-about-the-option-monad-transformer and https://softwarecorner.wordpress.com/2013/12/06/scalaz-optiont-monad-transformer/ for some examples.

分享给朋友:
您可能感兴趣的文章:
随机阅读: