在 Scala 中迭代 List 并等待 Future 完成

Iterating over a List and waiting for a Future to complete in Scala

我有以下场景:

我想遍历 List[Building] 并为每个元素调用 getAddress 并等待它完成,以便我可以创建一个类型为 BuildingWithAddress 的新对象并存储它在一个集合中,然后我将 return 给调用者。

我以为我会使用 for-comprehension,但事实证明,沿着这条线的东西不会真正起作用:

for {
    building <- listOfBuildings
    address <- getAddress(building)
    buildingWithAddress = BuildingWithAddress(name = building.name, town = address.town)
} yield buildingWithAddress

我也考虑过使用 flatMap 遍历列表,然后对地址执行相同的操作,但类型不同,这行不通。

我尝试使用 forEach 但是 forEach 又没有等待 Future 完成。

对于这样一个简单的用例,解决方案是什么?

您通常不能在单个 for 理解中组合不同的 monad(scala 类集合除外)。在这里你想组合 FutureList monads,这不能通过这种方式完成。 如果您想以 "sequential" 方式执行此操作(在开始新操作之前等待以前的未来操作完成),您需要使用来自 scalaz(或猫)的 ListT monad 转换器,如下所示:

import scalaz.ListT
import scalaz.std.scalaFuture._

implicit executor: ExecutionContext = ...

def fromList[A](x: List[A]) = ListT(Future.successful(x))
def single[A](x: Future[A]) = ListT(x.map(List(_)))

(for {
  building <- fromList(listOfBuildings)
  address <- single(getAddress(building))
  buildingWithAddress = BuildingWithAddress(name = building.name, town = address.town)
} yield buildingWithAddress).run

这将根据您的需要生成 Future[List[...]]。 如果您可以并行调用 getAddress 函数,则替代解决方案:

Future.traverse(listOfBuildings)(building =>
  getAddress(building).map(address =>
    BuildingWithAddress(name = building.name, town = address.town)))

这是遍历List"applicatively"(意思是:并行)

使用Future.sequence

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

val l = List(1,2,3)
val futureList: Seq[Future[Int]] = l.map(e=>Future(e))

//conversion List[Future[T]] => Future[List[T]]
val singleFuture: Future[Seq[Int]] = Future.sequence(futureList)
singleFuture.map(_.length)