2

Let's say I have a function func1 that needs to return a Future with two integers. Each of the two values are returned by independent futures, like so:

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

def func1 : Future[(Int,Int)] = {

    val future1 = f1
    future1.map { result1 =>
       result1 * 10
    }

    val future2 = f2
    future2.map { result2 =>
       result2 * 20
    }

} 

I need future1 wait until future2 ends (or vice versa) to return both results as (Int,Int). How can this be accomplished?

Cyrille Corpet
  • 5,265
  • 1
  • 14
  • 31
ps0604
  • 1,227
  • 23
  • 133
  • 330

3 Answers3

4

That's precisely what the zip method on futures does:

val futurePair: Future[(Int, Int)] = future1.zip(future2)

Note that if you haven't instantiated your futures before (say, if future1 and future2 are defs, not vals), this will run the two computations in parallel, while a for comprehension (or flatMap) would wait for the first one to succeed before starting the second one.

Cyrille Corpet
  • 5,265
  • 1
  • 14
  • 31
  • What happens if there are 3 or 4 futures? Is it possible to have a Tuple4? Or will it be nested tuples? – Tyler May 04 '17 at 20:48
  • It will be nested tuples. But you can always use `map` to give them the form you desire. If you have a `List[Future[Int]]`, you can even use the static method `Future.sequence` to wait for all of them into a `Future[List[Int]]`. – Cyrille Corpet May 04 '17 at 22:04
  • To explain what I mean about using `map`, you can do `future1 zip future2 zip future3 map { case ((a, b), c) => (a, b, c) }` – Cyrille Corpet May 05 '17 at 12:50
1

A for-comprehension is the best option here:

scala> import scala.concurrent.Future
import scala.concurrent.Future

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

scala> def f1 = Future{1}
f1: scala.concurrent.Future[Int]

scala> def f2 = Future{2}
f2: scala.concurrent.Future[Int]

scala> for {result1 <- f1; result2 <- f2} yield (result1 * 10, result2 * 20)
res0: scala.concurrent.Future[(Int, Int)] = scala.concurrent.impl.Promise$DefaultPromise@71f67a79

More information can be found here and here.

Note: this will run the two Futures in sequence while Cyrille Corpet's solution will run them in parallel.

Community
  • 1
  • 1
evan.oman
  • 5,922
  • 22
  • 43
1

You can use a for-comprehension for futures that have already started like this:

val f1: Future[Int] = ???
val f2: Future[Int] = ???
val f3: Future[Int] = ???

val futureInts: Future[(Int, Int, Int)] = for {
  result1 <- f1
  result2 <- f2
  result3 <- f3
} yield (result1, result2, result3)

If the futures were assigned to lazy vals or defs then this wouldn't work, because the futures would not have been started (if you start the futures inside the for comprehension, then they will be executed sequentially). Here is an example of starting them, and then waiting for them with for.

Example:

val f1: Future[Int] = Future {
  println("starting f1")
  Thread.sleep(1000)
  1
}
val f2: Future[Int] = Future {
  println("starting f2")
  Thread.sleep(3000)
  2
}
val f3: Future[Int] = Future {
  println("starting f3")
  Thread.sleep(2000)
  3
}

val futureInts: Future[(Int, Int, Int)] = for {
  result1 <- f1
  result2 <- f2
  result3 <- f3
} yield (result1, result2, result3)

futureInts.map {
  case tuple => println(tuple)
}

Output:

starting f1 // These first 
starting f3 // threes statements
starting f2 // happen right away.

(1,2,2)     // Then this prints a three seconds later

In your case you could do this:

def func1 : Future[(Int,Int)] = {

  // Start futures
  val future1 = f1.map(_ * 10)
  val future2 = f2.map(_ * 20)

  // Wait for both futures, and return a tuple
  for {
    result1 <- future1
    result2 <- future2
  } yield (result1, result2)

}
Tyler
  • 17,669
  • 10
  • 51
  • 89