【问题标题】:Best practices to use Future inside a Controller (Play+Scala)在控制器中使用 Future 的最佳实践(Play+Scala)
【发布时间】:2018-07-01 17:43:45
【问题描述】:

在我的 Play 网络应用程序中,我使用 val resultRack = Await.result(futureList, Duration.Inf) 从 Future 获取结果。是否有另一种更好的方法(使用最佳实践)从数据库中获取结果?如果我使用onCompleteonSuccess,我的控制器将完成执行,结果还没有在val 中。下面是我的Controller方法。一切正常,但我需要遵循 Scala 中的更多最佳实践。

已编辑:我已经在其他方法上使用Action.async。但是在这个我不能使用,主要是因为either.fold。我想我需要一个 map 包围该方法的所有代码,然后才能验证 json。

  def addRack = Action(parse.json) { request =>
    val either = request.body.validate[Rack]
    either.fold(
      errors => BadRequest("invalid json Rack.\n"),
      rack => {
        val f: Future[Option[RackRow]] = rackRepository.getById(rack.id)
        val result = Await.result(f, Duration.Inf)
        result match {
          case Some(r) =>
            // If the Rack already exists we update the produced and currentTime properties
            val fGpu: Future[Seq[GpuRow]] = gpuRepository.getByRack(r.id)
            // val total = fGpu.map(_.map(_.produced).sum)
            val resultGpu = Await.result(fGpu, Duration.Inf)
            val total = resultGpu.map(_.produced).sum
            rackRepository.update(r.id, Some(total), Some(System.currentTimeMillis))
            Ok("Rack already exists! Updated produced and currentTime.\n")
          case None =>
            // If the Rack does not exist we create it.
            val rackRow = RackRow(rack.id, rack.produced, System.currentTimeMillis)
            rackRepository.insert(rackRow)
            Ok
        }
      }
    )
  }

新方法使用 flatMap 和地图。我的问题是我正在控制器内创建和填充 seq rackSeq。我用来创建这个对象的gpuSeq 没有被评估,因为它来自未来。我应该如何评估这个未来gpuSeq?在我的结果中,我只能看到rackSeq,但gpuSeq 的列表始终为空。

此外,如果代码 Util.toTime(at) 抛出错误,我无法使用 recover 捕获此错误。据我了解,我可以这样做....

  def getRacks(at: String) = Action.async { implicit request: Request[AnyContent] =>

    var rackSeq: Seq[Rack] = Seq.empty
    var gpuSeq: Seq[Gpu] = Seq.empty

    rackRepository.get(Util.toTime(at)).flatMap { resultRack: Seq[RackRow] =>
      resultRack.map { r: RackRow =>
        gpuRepository.getByRack(r.id).map { result: Seq[GpuRow] =>
          result.map { gpuRow: GpuRow =>
            gpuSeq = gpuSeq :+ Gpu(gpuRow.id, gpuRow.rackId, gpuRow.produced, Util.toDate(gpuRow.installedAt))
            println(gpuRow)
          }
        }
        val rack = Rack(r.id, r.produced, Util.toDate(r.currentHour), gpuSeq)
        rackSeq = rackSeq :+ rack
      }

      //      val result = Await.result(listGpu, Duration.Inf)
      //      result.foreach { gpuRow =>
      //        gpuSeq = gpuSeq :+ Gpu(gpuRow.id, gpuRow.rackId, gpuRow.produced, Util.toDate(gpuRow.installedAt))
      //      }
      Future.successful(Ok(Json.toJson(rackSeq)).as(JSON))
    }.recover {
      case pe: ParseException => BadRequest(Json.toJson("Error on parse String to time."))
      case e: Exception => BadRequest(Json.toJson("Error to get racks."))
      case _ => BadRequest(Json.toJson("Unknow error to get racks."))
    }
  }

【问题讨论】:

  • 引用this 在类似情况下帮助了我很多

标签: scala playframework future


【解决方案1】:

永远不要在 Play 控制器中使用Await.result。这将阻塞线程并扼杀使用像 Play 这样的响应式框架的主要好处之一。而是将mapflatMapFuture 生成Result。例如,假设您有以下RackRepository

class RackRepository {
  def racks: Future[Seq[Rack]] = ???
}

在您的控制器中,而不是这样做:

def wrong = Action {
  val racks: Future[Seq[Rack]] = rackRepository.racks
  // This is wrong, don't do that
  val racksSeq = Await.result(racks, Duration.Inf)
  Ok(Json.toJson(racksSeq))
}

您所做的是,您使用Action.async 并映射您的未来以生成结果:

def list = Action.async {
  rackRepository.racks.map { racks =>
    Ok(Json.toJson(racks))
  }
}

如果您需要嵌套多个未来结果,请改用flatMap

编辑:

从您的第一个示例中,您需要了解mapflatMap 之间的区别。这看起来是一个好的开始:

Futures - map vs flatmap

让我们看一些例子:

val firstFuture: Future[String] = ??? // it does not mater where it comes from
val secondFuture: Future[String] = ??? // it does not mater where it comes from

val f1: Future[Int] = firstFuture.map(_.toInt)
val f2: Future[Future[String]] = firstFuture.map(secondFuture)
val f3: Future[String] = firstFuture.flatMap(secondFuture)

// Let's start to combine the future values
val f4: Future[Future[String]] = firstFuture.map { first =>
  secondFuture.map { second =>
    first + second // concatenate
  }
}

// But what if we want a Future[String] instead of a Future[Future[String]]?
// flatMap to the rescue!
val f5: Future[String] = firstFuture.flatMap { first =>
  secondFuture.map { second =>
    first + second // concatenate
  }
}

看到了吗?没有Await。然后我们有你的代码:

val fGpu: Future[Seq[GpuRow]] = gpuRepository.getByRack(r.id)
// val total = fGpu.map(_.map(_.produced).sum)
val resultGpu = Await.result(fGpu, Duration.Inf)

为什么不像我对f5 那样合并flatMapmap?换句话说,为什么要在fGpuAwait 而不是map 它返回一个Future[Result]

gpuRepository.getByRack(r.id).map { gpuRows =>
  val total = gpuRows.map(_.produced).sum
  rackRepository.update(r.id, Some(total), Some(System.currentTimeMillis))
  Ok("Rack already exists! Updated produced and currentTime.\n")
}

当然,f 需要使用Action.asyncflatMap

【讨论】:

  • 感谢您的回答。我现在正在使用地图和平面地图。我现在唯一的疑问是如何从 flatMap 返回值。我编辑了这个问题。你能帮帮我吗?
  • 您不会只返回flatMap 中的“值”,而是返回Future[T](其中T 是某种类型)。我们先来看mapfuture.map(Future.successful(Ok)) 的结果是Future[Future[Result]]。但是Action.async 只需要一个Future[Result],然后你需要使用flatMap,因为future.flatMap(Future.successful(Ok)) 返回一个Future[Result]。查看更多here
  • 嗨@mar​​cospereira,我编辑了问题以考虑使用flatMapmap。我想用这段代码我正在做你在回答中所说的。但我仍然看不到rackList 中的gpuList
  • 查看我的编辑。现在有足够的信息来弄清楚如何更正您的代码。
  • 我的问题出在第二个例子上。我第一个像你说的那样工作。也许最好打开一个新问题.....
【解决方案2】:

这里有几件事是关于你的代码,然后是关于你对未来的问题:

  1. 不要将控制器与模型混合:一般来说,控制器是一组方法(在控制器类中),它们获取请求并返回结果(OK,@987654322 @, 等等。)。模型是类/对象/接口中的一组方法,它们获取一组参数、处理外部资源并将其结果返回给控制器。

  2. 方法是你的朋友:您可以将代码更多地划分为不同的方法。例如,你的方法名称是addRack,但它的主体也包含了一些处理,你可以将它们放在不同的方法中,无论是在控制器中,还是在模型中;取决于他们属于哪里。

  3. 从不等待:这是有原因的,即您正在占用线程并且在等待期间不会将它们单独放置。这将导致您的应用在内存和 CPU 使用方面效率低下。

  4. Map 是你的朋友:在调用返回未来的方法时使用 map。比如你要调用这个方法:

def hiFromFuture : Future[String] = Future{...}

hiFromFuture.map{
  futureResult: String => //when Future is successful 
}

如果您以后有多个连续的电话,您也应该使用flatmap。例如,假设hiFromFuture2hiFromFuture 具有相同的签名/正文:

hiFromFuture.map{
  futureResult: String => hiFromFuture2.map{ futureResult => }

}

应该写成:

hiFromFuture.flatMap{
  futureResult: String => //when first future is successful 
    hiFromFuture2.map{ 
      futureResult => //when second future is successful 
    }
}

避免Future[Future[String]];并获取Future[String]

  1. 同样恢复,对于失败的未来:如果你没有得到结果怎么办?您使用恢复。例如:

    hiFromFuture.map{gotData => Ok("success")}.recover{case e: Exception => BadRequest}

请注意,您可以在恢复块中使用您期望的任何异常。

【讨论】:

  • 感谢您的回答戴夫。如何使用嵌套在地图中的 flatMap 返回 Ok?我编辑了问题以向您展示我的示例。
  • @FelipeOliveiraGutierrez 没问题。在flatMapmap 的正文中,您可以像往常一样编写结果(不将其包装在Future 中)。您是否收到任何具体的错误/警告?
  • 是的,我在写作时没有包装在 Future 中。我有这个编译错误:type mismatch; [error] found : play.api.mvc.Result. [error] required: scala.concurrent.Future[play.api.mvc.Result]。如果我这样写Future.successful(Ok(Json.toJson(rackSeq)).as(JSON)),我在rackSeq 里面什么都没有。我猜是因为rackSeq 在地图内部进行了处理。
  • 我编辑了问题的第二种方法,以更好地解释我所看到的。 getRacks 方法只返回 Rack,但不返回 gpuList。如果我使用 Await.result 阻塞线程,它只会返回 gpuList。我希望我能清楚我的问题是什么。
【解决方案3】:

如果您的问题是如何管理json验证的错误情况,在成功路径将返回Future的上下文中,您可以简单地将Result包装在已经成功完成的Future中,即@ 987654324@。如下所示

def addRack = Action(parse.json).async { request =>
  val either = request.body.validate[Rack]
  either.fold(
    errors => Future.successful(BadRequest("invalid json Rack.\n")),
    rack => {
      rackRepository.getById(rack.id).map {
        case Some(r) =>
          //...

          Ok("Rack already exists! Updated produced and currentTime.\n")
        case None =>
          //...

          Ok
      }
    }
  )
}

然后,当您嵌套期货时,您应该按照 marcospereira 和 Dave Rose 的说明进行平面图

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-04-16
    • 1970-01-01
    • 2018-09-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-30
    • 2014-03-09
    相关资源
    最近更新 更多