scala - 在 Controller 中使用 Future 的最佳实践 (Play+Scala)

标签 scala playframework future

在我的 Play 网络应用程序中,我正在使用 val resultRack = Await.result(futureList, Duration.Inf)从 Future 中获取结果。是否有另一种更好的方法(使用最佳实践)从数据库中获取结果?如果我使用 onCompleteonSuccess我的 Controller 完成执行,结果不在 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 和 map。我的问题是我正在创建和填充序列 rackSeq Controller 内部。 gpuSeq我用来创建这个对象的没有被评估,因为它来自 future 。我该怎么做才能评价这个 Future 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."))
    }
  }

最佳答案

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

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

在您的 Controller 中,而不是执行以下操作:
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并映射您的 future 以生成结果:
def list = Action.async {
  rackRepository.racks.map { racks =>
    Ok(Json.toJson(racks))
  }
}

如果您需要嵌套多个 future 结果,请使用 flatMap反而。

编辑:

从你的第一个例子,你需要做的是理解 map 之间的区别。和 flatMap .这看起来是一个好的开始:

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)

为什么不结合flatMapmap正如我为 f5 所做的那样?换句话说,为什么要AwaitfGpu而不是 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")
}

当然,你需要使用Action.asyncflatMapf .

关于scala - 在 Controller 中使用 Future 的最佳实践 (Play+Scala),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48392670/

相关文章:

scala - [A,Future [B]]到Future [E [[A,B]]

scala - 在 Spark 中为 DataFrame 模式定义 DateType 转换

scala - 创造 future 而无需开始

flutter - 返回 map 时,FutureBuilder中的空快照

json - 为 Play Json 库中的单例提供隐式值

java - JVM 框架,哪一个?

java - 在 Play 模板中将 boolean 值渲染为 0 或 1

scala - 如何在 Spark (Scala) 中读取带有自定义分隔符的新行和新列的文件

scala - akka 向远程参与者发送闭包

scala - 带有 Scala Actor 的客户端-服务器示例