2

I have the following futures:

def f1 = Future {1}    
def f2 = Future {2}

I need the following code to return Future[(Int,Int)] :

val future = // function that returns a future
future.flatMap {
    val x1 = f1
    val x2 = f2
    (x1,x2)  // This returns Future[Int],Future[Int] 
}

Instead of (Future[Int],Future[Int]) I need the function to return Future[(Int,Int)]. How to convert it?

oxbow_lakes
  • 133,303
  • 56
  • 317
  • 449
ps0604
  • 1,227
  • 23
  • 133
  • 330
  • 2
    Possible duplicate of [Waiting for another future to end to return a function](http://stackoverflow.com/questions/43790952/waiting-for-another-future-to-end-to-return-a-function) – Cyrille Corpet May 05 '17 at 08:32
  • I think you have accepted the wrong answer here - see mine :-) – oxbow_lakes May 05 '17 at 10:51

2 Answers2

2

This is a classic use case for a for comprehension.

for {
    x1 <- f1
    x2 <- f2
} yield (x1, x2)

This is equivalent to a flatmap followed by a map

f1.flatMap(x1 => f2.map(x2 => (x1,x2)))

Since you will not reach the inside of f1.flatMap until x1 is ready, this code will execute sequentially.

If you wish for the futures to execute concurrently, you can instantiated the futures before the for comprehension.

val future1 = f1
val future2 = f2
for {
    x1 <- future1
    x2 <- future2
} yield (x1, x2)
soote
  • 3,240
  • 1
  • 23
  • 34
  • It should be pointed out that this solution is sub-optimal because it will not start `f2` until `f1` has completed :-( – oxbow_lakes May 05 '17 at 10:18
  • @oxbow_lakes It is easy to choose the execution strategy with a for comprehension. You can simply instantiate the futures before the for comprehension to achieve concurrency. The question never mentioned that it wanted the futures to execute concurrently. – soote May 05 '17 at 12:30
  • @oxbow_lakes in many cases, sequential processing is the desired result. – soote May 05 '17 at 12:43
  • It's a sub-optimal solution; it's way more code, it reinvents the wheel and if you wanted sequential computation, you wouldn't be using futures – oxbow_lakes May 05 '17 at 20:08
  • @oxbow_lakes It is identical in performance, uses built in language constructs, and is more flexible as it scales to any number of futures easily. Many times I have needed to perform an async operation, then depending on its result, perform another async operation. – soote May 05 '17 at 20:36
2

I'm going to take issue with the (currently) accepted answer here. As per my comment, this is not really the correct way to zip together two futures. the correct way is simply this:

f1 zip f2

The other answer:

for (x <- f1; y <- f2) yield (x, y)

Whilst this will work, it is not parallel in the case that f2 is an expression yielding a future (as it is in this question). If this is the case, f2 will not be constructed until the first future has completed [1]. Whilst zip has been implemented in terms of flatMap in the same way, because its argument is strict, the second future is already running (subject to the execution context of course).

It's also more succinct!

[1] - this can be seen by observing that y, the value computed by f1, is in scope as f2 is constructed

Appendix

This can be demonstrated easily:

scala> import scala.concurrent._; import ExecutionContext.Implicits.global
import scala.concurrent._
import ExecutionContext.Implicits.global

scala> def createAndStartFuture(i: Int): Future[Int] = Future {
     |   println(s"starting $i in ${Thread.currentThread.getName} at ${java.time.Instant.now()}")
     |   Thread.sleep(20000L)
     |   i
     | }
createAndStartFuture: (i: Int)scala.concurrent.Future[Int]

With this:

scala> for (x <- createAndStartFuture(1); y <- createAndStartFuture(2)) yield (x, y)
starting 1 in scala-execution-context-global-34 at 2017-05-05T10:29:47.635Z
res15: scala.concurrent.Future[(Int, Int)] = Future(<not completed>)

// Waits 20s
starting 2 in scala-execution-context-global-32 at 2017-05-05T10:30:07.636Z

But with zip

scala> createAndStartFuture(1) zip createAndStartFuture(2)
starting 1 in scala-execution-context-global-34 at 2017-05-05T10:30:45.434Z
starting 2 in scala-execution-context-global-32 at 2017-05-05T10:30:45.434Z
res16: scala.concurrent.Future[(Int, Int)] = Future(<not completed>)
oxbow_lakes
  • 133,303
  • 56
  • 317
  • 449