Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get rid of Scala Future nesting

Tags:

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.

like image 898
DanielKhan Avatar asked Nov 29 '13 00:11

DanielKhan


People also ask

Is Scala Future blocking?

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.

Is Scala Future a Monad?

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.

What is the difference between a Java Future and a Scala Future?

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.


Video Answer


1 Answers

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]*/ } } 
like image 84
stew Avatar answered Nov 30 '22 23:11

stew