0

I have a method

 def readTree(id: String): Future[Option[CategoryTreeResponse]]

and a list of String channels:List[String].

How to iterate and combine all the results into a non Future Sequence ? such as :

 def readAllTrees(): Seq[CategoryTreeResponse] =  ???

Possibly without blocking.

Coming form the imperative world, I'd do like this :

import scala.concurrent.duration._

def readTrees(): Seq[CategoryTreeResponse] = {
  val list = ListBuffer[CategoryTreeResponse]()
  for (id <- channels) {
    val tree = Await.result(readTree(id), 5.seconds)
    if (tree.isDefined) {
      list += tree.get
    }
  }
  list
}
bad_coder
  • 11,289
  • 20
  • 44
  • 72
Raymond Chenon
  • 11,482
  • 15
  • 77
  • 110

2 Answers2

1

You could do something like this

def readAllTrees(channels: List[String]): Future[Seq[CategoryTreeResponse]] = {
    Future.sequence(channels.map(readTree(_))).map(_.flatten)
}

I have changed the signature of readAllTrees to receive the list and return a Future of the Sequence.

If you want to access to the resulting sequence you will need to wait until is finished doing

 Await.result(readAllTrees(channels), Duration.Inf)

But this is not a very nice way to manage futures because it will lock the thread that calls Await.ready

Mikel San Vicente
  • 3,831
  • 2
  • 21
  • 39
  • Nonetheless using `Await` is rarely a good solution to manage `Future`, but using `.get` on `Option` is unsafe – cchantep Jul 19 '17 at 19:02
  • ok, provided a safer way to access to the result, anyway as I noted, using Await.ready is not a good approach, I used it because there's no context enough to give a better solution – Mikel San Vicente Jul 19 '17 at 19:09
  • As I guessed the only way is to block with `Await.result` – Raymond Chenon Jul 20 '17 at 11:53
  • @Mikel , another issue with your solution : when you block `Future Sequence` , the timeout is the total timeout equals to `size of Sequence * individual timeout`. I have circuit breaker in front. Which is better : wait for each Future or wait for all whole Sequence ? – Raymond Chenon Jul 20 '17 at 16:39
  • if you wait for the composed Future all the tasks wil be executed concurrently and only one Thread will be blocked. If you wait for each Future you will either have to execute sequentially or have one thread blocked per Future. So I think it's clearly better to use the composed Future – Mikel San Vicente Jul 20 '17 at 17:07
1

Future.sequence and Await.result should help. I agree with Mikel though, it is better to stay async as long as possible using map/flatMap/foreach etc methods of the Future class

scala> :paste
// Entering paste mode (ctrl-D to finish)

import scala.concurrent._
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global

case class CategoryTreeResponse()

val futureResults: List[Future[Option[CategoryTreeResponse]]] = List(
 Future.successful(Option(CategoryTreeResponse())), 
 Future.successful(Option(CategoryTreeResponse())), 
 Future.successful(None)
) 
val futureResult: Future[List[Option[CategoryTreeResponse]]] = Future.sequence(futureResults)
val allResults: List[Option[CategoryTreeResponse]] = Await.result(futureResult, Duration.Inf)
val nonEmptyResults: Seq[CategoryTreeResponse] = allResults.flatMap(_.toSeq)

// Exiting paste mode, now interpreting.

import scala.concurrent._
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
defined class CategoryTreeResponse
futureResults: List[scala.concurrent.Future[Option[CategoryTreeResponse]]] = List(Future(Success(Some(CategoryTreeResponse()))), Future(Success(Some(CategoryTreeResponse()))), Future(Success(None)))
futureResult: scala.concurrent.Future[List[Option[CategoryTreeResponse]]] = Future(Success(List(Some(CategoryTreeResponse()), Some(CategoryTreeResponse()), None)))
allResults: List[Option[CategoryTreeResponse]] = List(Some(CategoryTreeResponse()), Some(CategoryTreeResponse()), None)
nonEmptyResults: Seq[CategoryTreeResponse] = List(CategoryTreeResponse(), CategoryTreeResponse())

scala> 
Denis Makarenko
  • 2,853
  • 15
  • 29