Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

scala's for yield comprehension used with Future. How to wait until future has returned?

Tags:

scala

I have a function which provides a Context:

def buildContext(s:String)(request:RequestHeader):Future[Granite.Context] = {
    .... // returns a Future[Granite.Context]
}

I then have another function which uses a Context to return an Option[Library.Document]:

def getDocument(tag: String):Option[Library.Document] = {
   val fakeRequest = play.api.test.FakeRequest().withHeaders(CONTENT_TYPE -> "application/json")

   val context = buildContext(tag)(fakeRequest)

   val maybeDoc = context.getDocument //getDocument is defined on Granite.Context to return an Option[Library.Document]

}

How would this code take into account if the Future has returned or not? I have seen for/yield used to wait for the return but I always assumed that a for/yield just flatmaps things together and has nothing really to do with waiting for Futures to return. I'm kinda stuck here and don't really no the correct question to ask!

like image 543
Zuriar Avatar asked Mar 14 '14 12:03

Zuriar


2 Answers

The other two answers are misleading. A for yield in Scala is a compiler primitive that gets transformed into map or flatMap chains. Do not use Await if you can avoid it, it's not a simple issue.

You are introducing blocking behaviour and you have yet to realise the systemic damage you are doing when blocking.

When it comes to Future, map and flatMap do different things:

map is executed when the future completes. It's an asynchronous way to do a type safe mapping.

val f: Future[A] = someFutureProducer
def convertAToB(a: A): B = {..}
f map { a => convertAToB(a) } 

flatMap

is what you use to chain things:

someFuture flatMap {
  _ => {
    someOtherFuture
  }
}

The equivalent of the above is:

for {
  result1 <- someFuture
  result2 <- someOtherFuture
} yield result2

In Play you would use Async to handle the above:

Async {
    someFuture.map(i => Ok("Got result: " + i))
}

Update

I misunderstood your usage of Play. Still, it doesn't change anything. You can still make your logic asynchronous.

someFuture onComplete {
  case Success(result) => // doSomething
  case Failure(err) => // log the error etc
}

The main difference when thinking asynchronously is that you always have to map and flatMap and do everything else inside Futures to get things done. The performance gain is massive.

The bigger your app, the bigger the gain.

like image 137
flavian Avatar answered Sep 22 '22 03:09

flavian


When using a for-comprehension on a Future, you're not waiting for it to finish, you're just saying: when it is finished, use it like this, and For-comprehension returns another Future in this case.

If you want to wait for a future to finish, you should use the Await as follows:

val resultContext = Await.result(context , timeout.duration)

Then run the getDocument method on it as such:

val maybeDoc = resultContext.getDocument

EDIT

The usual way to work with Futures however is to wait until the last moment before you Await. As pointed out by another answer here, Play Framework does the same thing by allowing you to return Future[Result]. So, a good way to do things would be to only use for-comprehensions and make your methods return Futures, etc, until the last moment when you want to finally return your result.

like image 43
Peter Avatar answered Sep 22 '22 03:09

Peter