Futures - map vs flatmap

user404345 picture user404345 · Jul 26, 2015 · Viewed 23.1k times · Source

I've read the docs about map and flatMap and I understand that flatMap is used for an operation that accepts a Future parameter and returns another Future. What I don't fully understand is why I would want to do this. Take this example:

  1. User hits my webservice asking to "do stuff"
  2. I download a file (which is slow)
  3. I process the file (which is CPU intensive)
  4. Render the result

I understand that I would want to use a future to download the file but I have have two options re processing it:

val downloadFuture = Future {/* downloadFile */}
val processFuture = downloadFuture map {/* processFile */}
processFuture onSuccess { case r => renderResult(r) }

or

val downloadFuture = Future {/* download the file */}
val processFuture = downloadFuture flatMap { Future {/* processFile */} }
processFuture onSuccess { case r => renderResult(r) }

By adding debug statements (Thread.currentThread().getId) I see that in both cases download, process and render occur in the same thread (using ExecutionContext.Implicits.global).

Would I use flatMap simply to decouple downloadFile and processFile and ensure that processFile always runs in a Future even if it was not mapped from downloadFile?

Answer

mattinbits picture mattinbits · Jul 26, 2015

If you have a future, let's say, Future[HttpResponse], and you want to specify what to do with that result when it is ready, such as write the body to a file, you may do something like responseF.map(response => write(response.body). However if write is also an asynchronous method which returns a future, this map call will return a type like Future[Future[Result]].

In the following code:

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

val numF = Future{ 3 }

val stringF = numF.map(n => Future(n.toString))

val flatStringF = numF.flatMap(n => Future(n.toString))

stringF is of type Future[Future[String]] while flatStringF is of type Future[String]. Most would agree, the second is more useful. Flat Map is therefore useful for composing multiple futures together.

When you use for comprehensions with Futures, under the hood flatMap is being used together with map.

import scala.concurrent.{Await, Future}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._

val threeF = Future(3)
val fourF = Future(4)
val fiveF = Future(5)

val resultF = for{
  three <- threeF
  four <- fourF
  five <- fiveF
}yield{
  three * four * five
}

Await.result(resultF, 3 seconds)

This code will yield 60.

Under the hood, scala translates this to

val resultF = threeF.flatMap(three => fourF.flatMap(four => fiveF.map(five => three * four * five)))