我可以将Action.async与多个期货一起使用吗?

时间:2016-02-15 21:45:05

标签: scala playframework future

previous SO question中,我得到了有关在PlayFramework中使用Scala Futures的建议,谢谢。现在情况变得更复杂了。让我们说,在我必须找到可以找到水果的地图之前:

def getMapData(coll: MongoCollection[Document], s: String): Future[Seq[Document]] = ...

def mapFruit(collection: MongoCollection[Document]) = Action.async {
  val fut = getMapData(collection, "fruit")
  fut.map { docs: Seq[Document] =>
    Ok(docs.toJson)
  } recover {
    case e => Console.err.println("FAIL: " + e.getMessage); BadRequest("FAIL")
  }
}

事实证明人们更关心苹果而不是香蕉或樱桃,所以如果地图上不应出现超过100个项目,人们希望苹果优先于香蕉和樱桃,但不超过百分之几的项目。一张地图应该是苹果。某些函数pickDocs确定了正确的混合。我认为这样的事情可能会奏效,但不会:

def mapApplesBananasCherries(collection: MongoCollection[Document]) = Action.async {
  val futA = getMapData(collection, "apples")
  val futB = getMapData(collection, "bananas")
  val futC = getMapData(collection, "cherries")
  futA.map { docsA: Seq[Document] =>
    futB.map { docsB: Seq[Document] =>
      futC.map { docsC: Seq[Document] =>
        val docsPicked = pickDocs(100, docsA, docsB, docsC)
        Ok(docsPicked.toJson)
      }
    }
    // won't compile without something here, e.g. Ok("whatever")
  } recover {
    case e => Console.err.println("FAIL: " + e.getMessage); BadRequest("FAIL")
  }
}

当我有一个未来时,生活很简单,但现在我有三个。我能做些什么来使(1)工作和(2)再次简单?在所有三个期货都有价值之前,我无法真正构建网络响应。

4 个答案:

答案 0 :(得分:2)

基本上,你应该使用flatMap

futA.flatMap { docsA: Seq[String] =>
  futB.flatMap { docsB: Seq[String] =>
    futC.map { docsC: Seq[String] =>
      docsPicked = pickDocs(100, docsA, docsB, docsC)
        Ok(docsPicked.toJson)
      }
    }
}

此外,您可以使用理解:

val res = for {
  docsA <- futA
  docsB <- futB
  docsC <- futC
} yield Ok(pickDocs(100, docsA, docsB, docsC).toJson)
res.recover {
  case e => Console.err.println("FAIL: " + e.getMessage); BadRequest("FAIL")
}

答案 1 :(得分:2)

如果我的理解是你想要优先执行苹果,樱桃和香蕉,我会把它编码为类似的

import scala.concurrent.{Await, Future}
import scala.util.Random
import scala.concurrent.duration._

object WaitingFutures extends App {

  implicit val ec = scala.concurrent.ExecutionContext.Implicits.global

  val apples = Future {50 + Random.nextInt(100)}
  val cherries = Future {50 + Random.nextInt(100)}
  val bananas =  Future {50 + Random.nextInt(100)}

  val mix = for {
    app <- apples
    cher <- if (app < 100) cherries else Future {0}
    ban <- if (app + cher < 100) bananas else Future {0}
  } yield (app,cher,ban)


  mix.onComplete {m =>
    println(s"mix ${m.get}")
  }

  Await.result(mix, 3 seconds)

}

如果苹果在未来完成时返回超过100,它不会等到樱桃或香蕉完成,而是返回假的未来0.如果它还不够,它会等到樱桃被执行等等。

NB我没有花太多精力在如何发出if信号,所以我正在使用虚拟未来,这可能不是最好的方法。

答案 2 :(得分:1)

这是期货和类似类别“包含价值”的常见模式(例如OptionList

要合并要使用flatMap方法的结果,结果代码为

def mapApplesBananasCherries(collection: MongoCollection[Document]) = Action.async {
  val futA = getMapData(collection, "apples")
  val futB = getMapData(collection, "bananas")
  val futC = getMapData(collection, "cherries")
  futA.flatMap { docsA =>
    futB.flatMap { docsB =>
      futC.map { docsC =>
        val docsPicked = pickDocs(100, docsA, docsB, docsC)
        Ok(docsPicked.toJson)
      }
    }
  } recover {
    case e => Console.err.println("FAIL: " + e.getMessage); BadRequest("FAIL")
  }
}

事实上,存在一种特殊的语法以使其更具可读性(称为 for-comprehension )是如此常见:以下代码等同于前面的代码片段

def mapApplesBananasCherries(collection: MongoCollection[Document]) = Action.async {
  val futA = getMapData(collection, "apples")
  val futB = getMapData(collection, "bananas")
  val futC = getMapData(collection, "cherries")
  for {
    apples <- futA
    bananas <- futB
    cherries <- futC
  } yield {
    val docsPicked = pickDocs(100, apples, bananas, cherries)
    Ok(docsPicked.toJson)
  } recover {
    case e => Console.err.println("FAIL: " + e.getMessage); BadRequest("FAIL")
  }
}

答案 3 :(得分:1)

这不会编译,因为嵌套的future块返回SQL0204N "SQL160215110206360" is an undefined name. SQLSTATE=42704 。如果您在期货上使用Future[Future[Future[Response]]],您的期货将不会嵌套。

如果您希望重复次数少一些,可以使用flatMap代替同时启动期货。您可以使用模式匹配来重新提取列表:

Future.sequence

或者您可以将val futureCollections = List("apples", "bananas", "cherries").map{ getMapData(collection, _) } Future.sequence(futureCollections) map { case docsA :: docsB :: docsC :: Nil => Ok(pickDocs(100, docsA, docsB, docsC).toJson) } recover { case e => Console.err.println("FAIL: " + e.getMessage); BadRequest("FAIL") } 函数传递给列表(按优先级排序)以供其选择。

pickDocs

Future.sequence(futureCollections) map { docLists => Ok(pickDocs(docLists, 100, 0.75f).toJson) } recover { case e => Console.err.println("FAIL: " + e.getMessage); BadRequest("FAIL") } 实施将占列表头部的一定百分比,除非在完整列表中没有足够的文档,其中需要更多,然后递归地应用相同的百分比老虎机名单。

pickDocs