【问题标题】:scala's for yield comprehension used with Future. How to wait until future has returned?scala 用于 Future 的产量理解。如何等到未来回归?
【发布时间】:2014-03-15 00:05:39
【问题描述】:

我有一个提供上下文的函数:

def buildContext(s:String)(request:RequestHeader):Future[Granite.Context] = {
    .... // returns a Future[Granite.Context]
}

然后我有另一个函数,它使用上下文返回一个 Option[Library.Document]:

def getDocument(tag: String):Option[Library.Document] = {
   val fakeRequest = play.api.test.FakeRequest().withHeaders(CONTENT_TYPE -> "application/json")

   val context = buildContext(tag)(fakeRequest)

   val maybeDoc = context.getDocument //getDocument is defined on Granite.Context to return an Option[Library.Document]

}

如果 Future 已返回,此代码将如何考虑?我曾经看到 for/yield 用于等待返回,但我一直认为 for/yield 只是将事物平面映射在一起,与等待 Futures 返回无关。我有点卡在这里,真的没有正确的问题要问!

【问题讨论】:

    标签: scala


    【解决方案1】:

    其他两个答案具有误导性。 Scala 中的for yield 是一个编译器原语,它被转换为mapflatMap 链。避免使用Await,这不是一个简单的问题。

    您正在引入阻塞行为,但您尚未意识到阻塞时正在造成的系统性损害。

    对于FuturemapflatMap,做不同的事情:

    地图 在未来完成时执行。这是一种进行类型安全映射的异步方式。

    val f: Future[A] = someFutureProducer
    def convertAToB(a: A): B = {..}
    f map { a => convertAToB(a) } 
    

    平面地图

    是你用来链接东西的东西:

    someFuture flatMap {
      _ => {
        someOtherFuture
      }
    }
    

    上面的等价物是:

    for {
      result1 <- someFuture
      result2 <- someOtherFuture
    } yield result2
    

    在 Play 中,您将使用 Async 来处理上述问题:

    Async {
        someFuture.map(i => Ok("Got result: " + i))
    }
    

    更新

    我误解了您对 Play 的使用。尽管如此,它并没有改变任何东西。您仍然可以使您的逻辑异步。

    someFuture onComplete {
      case Success(result) => // doSomething
      case Failure(err) => // log the error etc
    }
    

    异步思考的主要区别在于你总是必须mapflatMap 并在Futures 中执行其他所有操作才能完成任务。性能提升巨大。

    您的应用越大,收益就越大。

    【讨论】:

    • 如果我错了,请纠正我,但由于他没有使用 Play,等待是不可避免的。在 Play 中,我相信 Async 只是释放应用程序来处理其他请求,直到当前请求一切正常,但是,客户端仍将等待他的响应。
    • @Peter 不一定。要了解异步等待和阻塞等待之间的区别,请阅读stackoverflow.com/questions/21558858/…
    • 我很困惑,所以他不应该等待得到想要的结果吗?他将如何进行?而且我只谈论 Scala,不涉及 Play。
    • @Peter 基本上,你所有的逻辑都变成了异步链。如果您了解守护线程和应用程序生命周期,这实际上是一回事。 Web 服务器与它无关。
    • 是的,我明白了,但是这让我很困扰,假设我们有一个简单的程序,它会调用一个返回字符串 Future 的方法,这个方法做了很多事情,所以当方法被调用时,String 不是立即可用的,并且想象我们的程序必须只打印那个 String,如果这个程序不等待那个 Future 的结果,它将如何打印那个 String?顺便说一句,我现在真的很困惑。 :)
    【解决方案2】:

    Future 上使用 for-comprehension 时,您不会等待它完成,您只是说:完成后,像这样使用它,然后 For-comprehension 返回另一个 Future在这种情况下。

    如果您想等待未来完成,您应该使用Await,如下所示:

    val resultContext = Await.result(context , timeout.duration)
    

    然后在其上运行 getDocument 方法:

    val maybeDoc = resultContext.getDocument
    

    编辑

    然而,使用 Futures 的通常方法是等到最后一刻再使用 Await。正如此处另一个答案所指出的,Play Framework 通过允许您返回Future[Result] 来做同样的事情。因此,做事的一个好方法是只使用 for-comprehensions 并让您的方法返回 Futures 等,直到您想要最终返回结果的最后一刻。

    【讨论】:

    • 你为什么要选角? Await 已输入。
    【解决方案3】:

    您可以为此使用scala.concurrent.Await

    import scala.concurrent.duration._
    import scala.concurrent.Await
    
    def getDocument(tag: String):Option[Library.Document] = {
       val fakeRequest = play.api.test.FakeRequest().withHeaders(CONTENT_TYPE -> "application/json")
       val context = Await.result(buildContext(tag)(fakeRequest), 42.seconds)
       val maybeDoc = context.getDocument
    
    }
    

    但是Await 将在未来未完成时阻塞线程,所以最好将buildContext 设为同步操作,返回Granite.Context,或者将getDocument 设为异步,返回Future[Option[Library.Document]]

    【讨论】:

      【解决方案4】:

      一旦你在未来,你必须留在未来,或者你必须等到未来到来。

      等待通常是个坏主意,因为它会阻碍你的执行,所以你应该在以后工作。

      基本上,您应该更改 getDocument 方法以将 Future 返回为 getDocument(tag: String):Future[Option[Library.Document]] 之类的东西

      然后使用map ro flatMap,链接你未来的调用:

      return buildContext(tag)(fakeRequest).map(_.getDocument)

      如果buildContext 失败,map 将包装Failure

      然后调用

      getDocument("blah").onComplete {
          case Success(optionalDoc) => ...
          case Failure(e) =>...
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-01-02
        • 2013-02-15
        • 2017-06-24
        • 2017-09-15
        • 2017-08-12
        • 1970-01-01
        相关资源
        最近更新 更多