繁体   English   中英

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

[英]Can I use Action.async with multiple Futures?

在上一个SO问题中 ,我得到了有关将Scala Futures与PlayFramework一起使用的建议,谢谢。 现在事情变得更加复杂了。 假设在我只需要映射可以找到水果的位置之前:

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)再次变得简单? 在所有三个期货都具有价值之前,我无法真正构建网络响应。

基本上,您应该使用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")
}

如果我的理解是您要优先处理苹果,樱桃和香蕉,我会像这样编写代码

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的虚拟将来。如果还不够,它将等到执行樱桃之前,依此类推。

注意:我并没有在如何发出if信号上投入太多精力,所以我使用的是虚拟未来,这可能不是最好的方法。

对于期货和类似的包含“值”的类(例如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")
  }
}

这不会编译,因为您嵌套的future块正在返回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实现将占据列表pickDocs的百分比,然后将相同百分比递归应用于其余插槽列表。


def pickDocs[T](lists: List[List[T]], max: Int, dampPercentage: Float): List[T] = {
  lists match {
    case Nil => Nil
    case head :: tail =>
      val remainingLength = tail.flatten.length
      val x = max - remainingLength
      val y = math.ceil(max * dampPercentage).toInt
      val fromHere = head.take(x max y)
      fromHere ++ pickDocs(tail, max - fromHere.length, dampPercentage)
  }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM