Again and again I am struggling when a function relies on some future results. This usually boils down to a result like Future[Seq[Future[MyObject]]]
To get rid of that I now use Await inside a helper function to get a non-future object out and reduce the nesting.
It looks like this
def findAll(page: Int, perPage: Int): Future[Seq[Idea]] = { val ideas: Future[Seq[Idea]] = collection.find(Json.obj()) // [...] ideas.map(_.map { // UGLY? idea => { // THIS RETURNED A Future[JsObject] before val shortInfo: JsObject = UserDao.getShortInfo(idea.user_id) idea.copy(user_data = Some(shortInfo)) } }) }
This code works but to me it looks quite hacky. The two map calls are another flaw. I spent hours trying to figure out how to keep this completely asynchronous and returning a simple future Seq. How can this be solved using Play2 best practices?
Edit To make the usecase more clear:
I have an object A from mongodb (reactivemongo) and want to add information coming from another call to mongodb getShortInfo
. It's a classical "get user for this post" case that would be solved with a join in RDBMS. getShortInfo
naturally would produce a Future because of the call to the db. To reduce the nesting within findAll
I used Await(). Is this a good idea?
findAll
is called from an asynchronous Play action, converted into Json and sent over the wire.
def getIdeas(page: Int, perPage: Int) = Action.async { for { count <- IdeaDao.count ideas <- IdeaDao.findAll(page, perPage) } yield { Ok(Json.toJson(ideas)) } }
So I think returning a Seq[Future[X]]
from findAll won't bring better performance as I have to wait for the result anyways. Is this correct?
The usecase in short: Take a Future call returning a Sequence, use each element of the result to create another Future call, return the result to an asynchronous action in a way that no blocking situations should occur.
By default, futures and promises are non-blocking, making use of callbacks instead of typical blocking operations. To simplify the use of callbacks both syntactically and conceptually, Scala provides combinators such as flatMap , foreach , and filter used to compose futures in a non-blocking way.
yes, then it's a monad. @ElectricCoffee no. @PabloFernandez Scala's flatMap is Haskell's >>= , and Scala's for-comprehensions are equivalent to Haskell's do notation.
A Java Future works in a synchronous blocking way. It does not work in an asynchronous non-blocking way, whereas a Scala Future works in an asynchronous non-blocking way. If we want an asynchronous non-blocking feature, we should use Java 8's CompletableFuture.
Two handy functions on the Future companion object you should know could help here, the first, and easier to wrap your head around is Future.sequence
. It takes a sequnce of futures and returns a Future of a sequence. If are ending up with a Future[Seq[Future[MyObject]]]
, lets call that result
. then you can change this to a Future[Future[Seq[MyObject]]]
with result.map(Future.sequence(_))
Then to collapse a Future[Future[X]]
for any X, you can run "result.flatMap(identity)", in fact, you can do this for any M[M[X]]
to create a M[X]
as long as M
has flatMap
.
Another useful function here is Future.traverse
. It is basically the result of taking a Seq[A]
, mapping it to a Seq[Future[B]]
, then running Future.sequence to get a Future[Seq[B]]
So in your example, you'd have:
ideas.map{ Future.traverse(_){ idea => /*something that returns a Future[JsObject]*/ } }.flatMap(identity)
However, many times when you are running flatMap(identity), you could be turning a map into a flatMap, and this is the case here:
ideas.flatMap{ Future.traverse(_) { idea => /*something that returns a Future[JsOjbect]*/ } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With