【问题标题】:Scala Futures callback hellScala期货回调地狱
【发布时间】:2015-04-05 07:18:38
【问题描述】:

我已多次阅读有关 Scala Futures 减少回调问题的文章。我的代码开始出现问题。

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

a.map { b =>
  b.map { c =>
    c.map { d =>
      d.map { res =>
        res + 10
      }
    }
  }
} 

如何使这段代码更扁平?

//编辑@againstmethod

for{
  b <- a
  c <- b
  d <- c
  res <- d
} yield res + 10

这段代码不会编译

错误:(21, 8) 类型不匹配;找到:Option[Int] 必需:
scala.concurrent.Future[?] res ^

【问题讨论】:

  • 如果您想出了一个在将其粘贴到 IDE 时实际编译的示例,那么提出改进建议可能会更容易...
  • @KimStebel 刚刚改了代码,所以更通用
  • 您首先应该避免堆叠Future,使用Future.traverseFuture.sequence
  • 没有人会写出像这样的真实代码。最好尝试一些实际上可能是真实的东西,然后用那个例子来询问你不理解的部分。
  • 这是我必须处理的代码的简化版本。而且它几乎是使用 reactivemongo 的未经修改的示例,所以是的,人们确实会编写这样的代码。

标签: scala callback future


【解决方案1】:

您可以使用for comprehension。例如:

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)
}

结果将是 13。

任何实现正确的mapflatMap 函数的类都可以在for 中以这种方式使用。

如果您不介意其他依赖项,您还可以使用 scalaz 之类的库并显式使用 monadic 绑定来扁平化(EDIT 编码了一些 Option 类型以解决下面的评论):

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)
}

最后,如果您只是想要在所有都成功后独立绑定的并行操作,您可以使用 scalaz applicative builder:

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

这将使所有 3 个期货完成,然后将块应用于 3 个参数。

【讨论】:

  • 是的,这是真的。但是对于理解,失败不会返回任何东西,对吧?
  • 正确,如果你这样创建它们,它们将连续执行。这只是另一个可能有所帮助的工具。
  • result 是一个 Future 在有意义的方式;您可以使用result.onFailure 或类似方法来处理故障。他们不只是默默地消失。
  • 但是你确实失去了粒度。
  • 但这不允许我提取像 Future( Option ( Future ( Option ( 10 ) ) ) ) 这样的值,只有 Future(Future(10))Option(Option(10)) 而这里不是这种情况
【解决方案2】:

其实答案很直接。

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

似乎已经足够了,因为当x.get + 10 失败时(因为None + 10),future 就会失败。所以使用简单的后备仍然有效

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

【讨论】:

    【解决方案3】:

    我还没有使用它们,但它们应该正是您正在寻找的:Monad 转换器。

    基本上,monad 转换器接受 Monad(例如 Future)并向其添加功能,例如 Option 提供的功能,然后返回转换后的 Monad。我认为 Scalaz 中甚至还有一个 Option Monad 转换器。这应该允许您在 Futures 中使用嵌套选项,并且仍然具有使用 for 理解的扁平代码结构。

    有关示例,请参阅 http://blog.garillot.net/post/91731853561/a-question-about-the-option-monad-transformerhttps://softwarecorner.wordpress.com/2013/12/06/scalaz-optiont-monad-transformer/

    【讨论】:

      猜你喜欢
      • 2016-07-11
      • 2017-04-06
      • 2017-05-08
      • 1970-01-01
      • 1970-01-01
      • 2013-12-16
      • 2013-08-08
      • 2018-06-26
      • 2017-01-01
      相关资源
      最近更新 更多