Scala - 按顺序依次执行任意数量的Futures

时间:2014-10-29 02:46:35

标签: scala future sequential for-comprehension foldleft

我试图找出按顺序执行一系列期货的最佳方式,其中一个Future的执行取决于之前的。我试图为任意数量的期货做这件事。

用户案例:

  • 我从我的数据库中检索了许多ID。
  • 我现在需要在Web服务上检索一些相关数据。
  • 我想在找到有效结果后停止。
  • 我只关心成功的结果。

并行执行这些并且然后解析返回的结果集合不是一个选项。我必须一次执行一个请求,并且只有在前一个请求没有返回任何结果时才执行下一个请求。

目前的解决方案是这些方面。使用foldLeft执行请求,然后仅在前一个未来符合某些条件时评估下一个未来。

def dblFuture(i: Int) = { i * 2 }
val list = List(1,2,3,4,5)
val future = list.foldLeft(Future(0)) {
  (previousFuture, next) => {
    for {
      previousResult <- previousFuture
      nextFuture <- { if (previousResult <= 4) dblFuture(next) else previousFuture }
    } yield (nextFuture)
  }
}

这方面的一个重要缺点是:a)即使我得到了满意的结果,我也会继续处理所有项目,并且b)一旦我找到了结果我就会这样做之后,我一直在评估谓词。在这种情况下,它是一个简单的if,但实际上它可能更复杂。

我觉得我错过了一个更为优雅的解决方案。

2 个答案:

答案 0 :(得分:5)

看看你的例子,似乎前面的结果对后续结果没有影响,相反,唯一重要的是前一个结果满足某些条件以防止计算下一个结果。如果是这种情况,这里是使用filterrecoverWith的递归解决方案。

def untilFirstSuccess[A, B](f: A => Future[B])(condition: B => Boolean)(list: List[A]): Future[B] = {
    list match {
        case head :: tail => f(head).filter(condition).recoverWith { case _: Throwable => untilFirstSuccess(f)(condition)(tail) }
        case Nil => Future.failed(new Exception("All failed.."))
    }
 }
仅在filter完成后才会调用

Future,只有在recoverWith失败时才会调用Future

def dblFuture(i: Int): Future[Int] = Future { 
     println("Executing.. " + i)
     i * 2 
 }

val list = List(1, 2, 3, 4, 5)

scala> untilFirstSuccess(dblFuture)(_ > 6)(list)
Executing.. 1
Executing.. 2
Executing.. 3
Executing.. 4
res1: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@514f4e98

scala> res1.value
res2: Option[scala.util.Try[Int]] = Some(Success(8))

答案 1 :(得分:2)

最好的方式,以及&#34;真正的函数式编程&#34;是scalaz-stream;)但是你需要从scala Future切换到scalaz.concurrent.Task以获取&#34; future结果&#34;。它有点不同。任务是纯粹的,未来是&#34;运行计算&#34;,但它们有很多共同之处。

  import scalaz.concurrent.Task
  import scalaz.stream.Process

  def dblTask(i: Int) = Task {
    println(s"Executing task $i")
    i * 2
  }

  val list = Seq(1,2,3,4,5)

  val p: Process[Task, Int] = Process.emitAll(list)

  val result: Task[Option[Int]] =
    p.flatMap(i => Process.eval(dblTask(i))).takeWhile(_ < 10).runLast

  println(s"result = ${result.run}")

结果:

Executing task 1
Executing task 2
Executing task 3
Executing task 4
Executing task 5
result = Some(8)

如果你的计算已经是scala Future,你可以将它转换为Task

implicit class Transformer[+T](fut: => SFuture[T]) {
  def toTask(implicit ec: scala.concurrent.ExecutionContext): Task[T] = {
    import scala.util.{Failure, Success}
    import scalaz.syntax.either._
    Task.async {
      register =>
        fut.onComplete {
          case Success(v) => register(v.right)
          case Failure(ex) => register(ex.left)
        }
    }
  }
}