简体   繁体   中英

Chaining Scala nested futures

I have some troubles chaining futures in Scala. Suppose I have the following:

def fetchInts(s: String): Future[List[Int]] = Future {
  (0 to s.length).toList
}

def fetchChars(a: Int): Future[List[Char]] = Future {
  ("a" * a).toList
}

and for a given String I want to fetch Int s and then for each fetched Int I want to fetch Char s, all as async as possible. The output type I want to get is Future[List[(Int, List[Char])]] and semantically I want to do something like that:

def fetch(s: String): Future[List[(Int, List[Char])]] = {
  for {
    as <- fetchInts(s)
    a <- as
    cs <- fetchChars(a)
  } yield (a, cs)
}

The above will not type-check as I mix List and Future monads so I have so far come up with:

def fetch(s: String): Future[List[(Int, List[Char])]] = {
  val fas: Future[List[Int]] = fetchInts(s)
  fas.flatMap { as: List[Int] =>
    val res = as.map { a: Int =>
      val fcs: Future[List[Char]] = fetchChars(a)
      fcs.flatMap(cs => Future((a, cs)))
    }
    Future.sequence(res)
  }
}

This type-checks but looks clumsy and I have some doubts if this is thread-optimal as I do not fully grasp the semantics of Futures.sequence() . Is there some other simpler, idiomatic way to perform this?

for-comprehension in Scala, is maps and flatMaps over the same entity. In your fetch method, it is over futures. The reason it doesn't work, is that as is List[Int] , and not a future. In this use case, it will be easier to write it without for-comprehension. You can try the following:

def fetch(s: String): Future[List[(Int, List[Char])]] = {
  fetchInts(s).flatMap { ints =>
    Future.traverse(ints)(i =>
      fetchChars(i).map { chars =>
        i -> chars
      }
    )
  }
}

The result of:

println(Await.result(fetch("abrakadabra"), 2.seconds))

Is:

List((0,List()), (1,List(a)), (2,List(a, a)), (3,List(a, a, a)), (4,List(a, a, a, a)), (5,List(a, a, a, a, a)), (6,List(a, a, a, a, a, a)), (7,List(a, a, a, a, a, a, a)), (8,List(a, a, a, a, a, a, a, a)), (9,List(a, a, a, a, a, a, a, a, a)), (10,List(a, a, a, a, a, a, a, a, a, a)), (11,List(a, a, a, a, a, a, a, a, a, a, a)))

From Scala docs Future.traverse :

Asynchronously and non-blockingly transforms a IterableOnce[A] into a Future[IterableOnce[B]] using the provided function A => Future[B]. This is useful for performing a parallel map.

Code run at Scastie .

If after all you do want to use for comprehension, you can try:

def fetch1(s: String): Future[List[(Int, List[Char])]] = {
  for {
    ints <- fetchInts(s)
    result <- Future.traverse(ints)(i => for {
        chars <- fetchChars(i)
      } yield i -> chars)
  } yield result
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM