未来递归模式/未来任意长度的链接

时间:2014-06-19 13:51:47

标签: scala recursion akka future

我很好奇递归建立一个连续运行的Akka期货链的最好方法,如果未来doWork调用失败,未来应该重试3次,链条应该如果重试尝试用完,则会失败。假设所有doWork次呼叫都通过,则返回的未来futChain应该只完成。

object Main extends App {
  val futChain = recurse(2)

  def recurse(param: Int, retries: Int = 3): Future[String] {
    Future {
      doWorkThatMayFailReturningString(param...)
    } recoverWith {
      case e => 
        if (retries > 0) recurse(param, retries -1)
        else  Future.failed(e)
    } flatMap {
      strRes => recurse(nextParam) //how should the res from the previous fut be passed?
    }
  }

  futChain onComplete {
    case res => println(res) //should print all the strings
  }
}
  1. 如何将结果作为集合获取?即在此示例中,每个StringdoWork函数返回(我需要以某种方式修改recurse func以返回Futrue[List[String]]
  2. 我应该使用recover还是recoverWith
  3. 调用flatMap链接这些来电是否可以
  4. 我应该考虑尾部递归和堆栈溢出?
  5. 我会更好地递归建立一份期货清单并减少它们吗?

1 个答案:

答案 0 :(得分:11)

您可以像这样实施可重试的Future

def retry[T](f: => Future[T])(n: Int)(implicit e: ExecutionContext): Future[T] = {
    n match {
        case i if (i > 1) => f.recoverWith{ case t: Throwable => retry(f)(n - 1)}
        case _ => f
    }       
}

这不是针对尾递归进行优化的,但是如果你只打算重试几次,你就不会得到堆栈溢出(我想如果它在前几次失败,无论如何,它会继续失败。

然后我会分开进行链接。如果您将有限数量的函数链接在一起,每个函数都取决于之前的(并且由于某种原因您希望聚合结果),您可以使用for理解(flatMap的语法糖):< / p>

for {
    firstResult <- retry(Future(doWork(param)))(3)
    secondResult <- retry(Future(doWork(firstResult)))(3)
    thirdResult <- retry(Future(doWork(secondResult)))(3)
} yield List(firstResult, secondResult, thirdResult)

对于任意长链,您可以使用Future.sequence(Akka库中的Futures)并行执行这些链:

def doWork(param: String): String = ...

val parameters: List[String] = List(...)

val results: Future[List[String]] = Future.sequence(parameters.map(doWork(_)))

这将解开List[Future[String]]Future[List[String]]的内容。

这是按顺序执行类似操作的一种方法:

def sequential[A, B](seq: List[A])(f: A => Future[B])(implicit e: ExecutionContext): Future[List[B]] = {
    seq.foldLeft(Future.successful(List[B]())) { case (left, next) =>
        left.flatMap(list => f(next).map(_ :: list))
    }
}

def doWork(param: String): String = ...

val results: Future[List[String]] = sequential(parameters)(param => Future(doWork(param))) 

这些功能的实现非常对您的用例敏感。如果链中的任何期货失败,上述两个函数将返回失败的期货。有时候你会想要这个,有时则不然。如果您只想收集成功的期货,并在不失败整个结果的情况下丢弃失败的期货,您可以添加额外的步骤来恢复失败。

此外,recoverrecoverWith之间的差异是它接受的PartialFunction类型。 recover使用默认值替换失败的期货,recoverWith使用其他Future替换失败的期货。对于我的retryrecoverWith更合适,因为我尝试自行恢复失败的Future

相关问题