Scala Play: how to wait until future is complete b

2019-08-19 10:11发布

问题:

In my playframework application I want to wait until my future is completed and the return it to the view.

my code looks like:

 def getContentComponentUsageSearch: Action[AnyContent] = Action.async { implicit request =>
    println(request.body.asJson)
    request.body.asJson.map(_.validate[StepIds] match {
      case JsSuccess(stepIds, _) =>

        println("VALIDE SUCCESS -------------------------------")


        val fList: List[Seq[Future[ProcessTemplatesModel]]] = List() :+ stepIds.s.map(s => {
          processTemplateDTO.getProcessStepTemplate(s.processStep_id).flatMap(stepTemplate => {
            processTemplateDTO.getProcessTemplate(stepTemplate.get.processTemplate_id.get).map(a => {
              a.get
            })
          })
        })


        fList.map(u => {
          val a: Seq[Future[ProcessTemplatesModel]] = u

          Future.sequence(a).map(s => {
            println(s)
          })
        })


        Future.successful(Ok(Json.obj("id" -> "")))

      case JsError(_) =>
        println("NOT VALID -------------------------------")
        Future.successful(BadRequest("Process Template not create client"))
      case _ => Future.successful(BadRequest("Process Template create client"))
    }).getOrElse(Future.successful(BadRequest("Process Template create client")))
  }

the pirntln(s) is printing the finished stuff. But how can I wait until it is complete and return it then to the view?

thanks in advance

UPDATE:

also tried this:

  val process = for {

      fList: List[Seq[Future[ProcessTemplatesModel]]] <- List() :+ stepIds.s.map(s => {
        processTemplateDTO.getProcessStepTemplate(s.processStep_id).flatMap(stepTemplate => {
          processTemplateDTO.getProcessTemplate(stepTemplate.get.processTemplate_id.get).map(a => {
            a.get
          })
        })
      })

    } yield (fList)





    process.map({ case (fList) =>
      Ok(Json.obj(
        "processTemplate" -> fList
      ))
    })

but then I got this:

UPDATE: My problem is that the futures in fList do not complete before an OK result is returned

回答1:

The code in the question didn't seem compilable, so here is an untested very rough sketch, that hopefully provides enough inspiration for further search of the correct solution:

def getContentComponentUsageSearch: = Action.async { implicit req =>
  req.body.asJson.map(_.validate[StepIds] match {
    case JsSuccess(stepIds, _) => {

      // Create list of futures
      val listFuts: List[Future[ProcessTemplatesModel]] = (stepIds.s.map(s => {
        processTemplateDTO.
          getProcessStepTemplate(s.processStep_id).
          flatMap{ stepTemplate => 
            processTemplateDTO.
              getProcessTemplate(stepTemplate.get.processTemplate_id.get).
              map(_.get)
          }
      })).toList

      // Sequence all the futures into a single future of list
      val futList = Future.sequence(listFuts)

      // Flat map this single future to the OK result
      for {
        listPTMs <- futList
      } yield {
        // Apparently some debug output? 
        listPTMs foreach printl

        Ok(Json.obj("id" -> ""))
      }
    }

    case JsError(_) => {
      println("NOT VALID -------------------------------")
      Future.successful(BadRequest("Process Template not create client"))
    }

    case _ => Future.successful(BadRequest("Process Template create client"))

  }).getOrElse(Future.successful(BadRequest("Process Template create client")))
}

If I understood your question correctly, what you wanted was to make sure that all futures in the list complete before you return the OK. Therefore I have first created a List[Future[...]]:

  val listFuts: List[Future[ProcessTemplatesModel]] = // ...

Then I've combined all the futures into a single future of list, which completes only when every element has completed:

  // Sequence all the futures into a single future of list
  val futList = Future.sequence(listFuts)

Then I've used a for-comprehension to make sure that the listPTMs finishes computation before the OK is returned:

  // Flat map this single future to the OK result
  for {
    listPTMs <- futList
  } yield {
    // Apparently some debug output? 
    listPTMs foreach printl

    Ok(Json.obj("id" -> ""))
  }

The for-yield (equivalent to map here) is what establishes the finish-this-before-doing-that behavior, so that listPTMs is fully evaluated before OK is constructed.



回答2:

In order to wait until a Future is complete, it is most common to do one of two things:

Use a for-comprehension, which does a bunch of mapping and flatmapping behind the scenes before doing anything in the yield section (see Andrey's comment for a more detailed explanation). A simplified example:

def index: Action[AnyContent] = Action.async {    
  val future1 = Future(1)
  val future2 = Future(2)

  for {
    f1 <- future1
    f2 <- future2
  } yield {
    println(s"$f1 + $f2 = ${f1 + f2}") // prints 3
    Ok(views.html.index("Home"))
  }
}

Map inside a Future:

def index: Action[AnyContent] = Action.async {    
  val future1 = Future(1)

  future1.map{
    f1 =>
    println(s"$f1")
    Ok(views.html.index("Home"))
  }
}

If there are multiple Futures:

def index: Action[AnyContent] = Action.async {

  val future1 = Future(1)
  val future2 = Future(2)

  future1.flatMap{
    f1 =>
      future2.map {
        f2 =>
          println(s"$f1 + $f2 = ${f1 + f2}")
          Ok(views.html.index("Home"))
      }
    }
  }
}

When you have multiple Futures though, the argument for for-yield comprehensions gets much stronger as it gets easier to read. Also, you are probably aware but if you work with futures you may need to following imports:

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global