Felix Felix - 1 month ago 9
Scala Question

How to solve Scala asynchronous behaviour

In my backend controller function I have the following code:

def getContentComponents = Action.async {
contentComponentDTO.list().map { contentComponentsFuture =>
contentComponentsFuture.foreach(contentComponentFuture =>
contentComponentFuture.typeOf match {
case 5 => contentComponentDTO.getContentComponentText(contentComponentFuture.id.get).map(text => {
contentComponentFuture.text = text.text
println(contentComponentFuture.text)
})

}
)
Ok(Json.toJson(contentComponentsFuture))
}
}


The problem is, that
OK()
is called before the stuff above is finished. Is there a smart way to wait until the
foreach
is finished?

thanks

That have been two differen questions with different problems! So thats the reason for two questions looking similar

Answer Source

There are two approaches that you can take, but before going to that let's recap what you are trying to do.

  1. You have a list of items contentComponentsFuture, I am assuming retrieving from database, which is why you are getting a future.
  2. Now your contentComponentsFuture is a mutable variable (which I strongly suggest not to use, stick to immutable data) has a text field which you need to update. Now all that code block before the Ok() will return a future as it is working on a future list. So the easiest solution is to do a map over the future and return the result. The map over a future is just a onComplete function which triggers once the future is resolved. So the code will look like:

    def getContentComponents = Action.async {
    val futureResult = contentComponentDTO.list().map { contentComponentsFuture =>
      contentComponentsFuture.map(contentComponentFuture =>
        contentComponentFuture.typeOf match {
          case 5 => contentComponentDTO.getContentComponentText(contentComponentFuture.id.get).map(text => {
            contentComponentFuture.text = text.text
            contentComponentFuture
          })
        }
      )
    }
        futureResult.map(result => {
    Future.sequence(result).map(t => Ok(Json.toJson(t))
    

    })) }

The other option will be to use scala asycn library: https://github.com/scala/scala-async which gives a handy wrapper so that you do not need to map over future explicitly, the same code above with scala async library will look like below:

def getContentComponents = Action.async {
    Async.async {
    val result = Async.await(contentComponentDTO.list().map { contentComponentsFuture =>
      contentComponentsFuture.map(contentComponentFuture =>
        contentComponentFuture.typeOf match {
          case 5 => contentComponentDTO.getContentComponentText(contentComponentFuture.id.get).map(text => {
            contentComponentFuture.text = text.text
            contentComponentFuture
          })
        }
      )
    })

    Ok(Json.toJson(result))
  }
  }