Last Updated: February 06, 2018
·
1.65K
· prayagupd

scala Seq[Future] to Future[Seq]

I have an API which gives me Future[Int], since I have to operate it on sequence of items, my resultant would be Seq[Future[Int]].

When I expose my API it makes more sense to have Future[Seq[Int]] instead of Seq[Future[Int]].

But the trick here is when I translate Seq[Future] to Future[Seq], future has to two states

1) Failure
2) Success

What if the Seq[Future] has one Future.Failure. Here's the example

scala> val task : Seq[Future[Int]] = (1 to 3).map(x => if (x == 3) Future.failed(new RuntimeException("error")) else Future(x))
task: Seq[scala.concurrent.Future[Int]] = Vector(Future(Success(1)), Future(Success(2)), Future(Failure(java.lang.RuntimeException: error)))

scala> Future.sequence(task)
res6: scala.concurrent.Future[Seq[Int]] = Future(<not completed>)

scala> res6
res7: scala.concurrent.Future[Seq[Int]] = Future(Failure(java.lang.RuntimeException: error))

The problem here is that Future.sequence is Future.Failure if it finds one single failure in a sequence.

Solution

scala> import scala.util.Try
import scala.util.Try

scala> import scala.util.Success
import scala.util.Success

scala> import scala.util.Failure
import scala.util.Failure

scala> val task: Seq[Future[Try[Int]]] = (1 to 3).map(x => if (x == 3) Future.successful(Failure(new RuntimeException("error"))) else Future(Success(x)))
task: Seq[scala.concurrent.Future[scala.util.Try[Int]]] = Vector(Future(Success(Success(1))), Future(Success(Success(2))), Future(Success(Failure(java.lang.RuntimeException: error))))

scala> Future.sequence(task)
res8: scala.concurrent.Future[Seq[scala.util.Try[Int]]] = Future(<not completed>)

scala> res8
res9: scala.concurrent.Future[Seq[scala.util.Try[Int]]] = Future(Success(Vector(Success(1), Success(2), Failure(java.lang.RuntimeException: error))))

Also, I can filter failures now,

scala> res8.map(_.filter(_.isSuccess))
res11: scala.concurrent.Future[Seq[scala.util.Try[Int]]] = Future(<not completed>)

scala> res11
res12: scala.concurrent.Future[Seq[scala.util.Try[Int]]] = Future(Success(Vector(Success(1), Success(2))))

Now the result can be simplified to Future[Seq],

scala> res8.map(_.filter(_.isSuccess).map(_.get))
res13: scala.concurrent.Future[Seq[Int]] = Future(<not completed>)

scala> res13
res14: scala.concurrent.Future[Seq[Int]] = Future(Success(Vector(1, 2)))