描述我的问题的简单代码示例:
import scala.util._
import scala.concurrent._
import scala.concurrent.duration._
import ExecutionContext.Implicits.global
class LoserException(msg: String, dice: Int) extends Exception(msg) { def diceRoll: Int = dice }
def aPlayThatMayFail: Future[Int] = {
Thread.sleep(1000) //throwing a dice takes some time...
//throw a dice:
(1 + Random.nextInt(6)) match {
case 6 => Future.successful(6) //I win!
case i: Int => Future.failed(new LoserException("I did not get 6...", i))
}
}
def win(prefix: String): String = {
val futureGameLog = aPlayThatMayFail
futureGameLog.onComplete(t => t match {
case Success(diceRoll) => "%s, and finally, I won! I rolled %d !!!".format(prefix, diceRoll)
case Failure(e) => e match {
case ex: LoserException => win("%s, and then i got %d".format(prefix, ex.diceRoll))
case _: Throwable => "%s, and then somebody cheated!!!".format(prefix)
}
})
"I want to do something like futureGameLog.waitForRecursiveResult, using Await.result or something like that..."
}
win("I started playing the dice")
这个简单的例子说明了我想做的事情。基本上,如果用文字表达,我想等待一些计算的结果,当我对先前的成功或失败的成功构成不同的行动时。
那么你将如何实现win
方法?
我的“真实世界”问题,如果它有任何区别,就是使用dispatch
进行异步http调用,我希望在前一个调用结束时继续进行http调用,但操作与之前的http不同呼叫成功与否。
答案 0 :(得分:6)
您可以通过递归调用恢复失败的未来:
def foo(x: Int) = x match {
case 10 => Future.successful(x)
case _ => Future.failed[Int](new Exception)
}
def bar(x: Int): Future[Int] = {
foo(x) recoverWith { case _ => bar(x+1) }
}
scala> bar(0)
res0: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@64d6601
scala> res0.value
res1: Option[scala.util.Try[Int]] = Some(Success(10))
recoverWith
需要PartialFunction[Throwable,scala.concurrent.Future[A]]
并返回Future[A]
。你应该小心,因为它会在这里进行大量的递归调用时使用相当多的内存。
答案 1 :(得分:2)
当drexin回答有关异常处理和恢复的部分时,让我尝试回答有关涉及期货的递归函数的部分。我相信使用Promise
会帮助您实现目标。重组后的代码如下所示:
def win(prefix: String): String = {
val prom = Promise[String]()
def doWin(p:String) {
val futureGameLog = aPlayThatMayFail
futureGameLog.onComplete(t => t match {
case Success(diceRoll) => prom.success("%s, and finally, I won! I rolled %d !!!".format(prefix, diceRoll))
case Failure(e) => e match {
case ex: LoserException => doWin("%s, and then i got %d".format(prefix, ex.diceRoll))
case other => prom.failure(new Exception("%s, and then somebody cheated!!!".format(prefix)))
}
})
}
doWin(prefix)
Await.result(prom.future, someTimeout)
}
现在这不会是真正的递归,因为它将构建一个长堆栈,因为期货是异步的,但它类似于精神中的递归。在这里使用promise会给你一些阻止的东西,而递归就是这样,阻止了调用者在场景背后发生的事情。
现在,如果我这样做,我很可能会重新定义这样的事情:
def win(prefix: String): Future[String] = {
val prom = Promise[String]()
def doWin(p:String) {
val futureGameLog = aPlayThatMayFail
futureGameLog.onComplete(t => t match {
case Success(diceRoll) => prom.success("%s, and finally, I won! I rolled %d !!!".format(prefix, diceRoll))
case Failure(e) => e match {
case ex: LoserException => doWin("%s, and then i got %d".format(prefix, ex.diceRoll))
case other => prom.failure(new Exception("%s, and then somebody cheated!!!".format(prefix)))
}
})
}
doWin(prefix)
prom.future
}
通过这种方式,您可以推迟决定是否阻止或使用此函数调用者的异步回调。这样更灵活,但它也使调用者暴露于您正在进行异步计算的事实,并且我不确定您的方案是否可以接受。我会把这个决定留给你。
答案 2 :(得分:1)
这对我有用:
def retryWithFuture[T](f: => Future[T],retries:Int, delay:FiniteDuration) (implicit ec: ExecutionContext, s: Scheduler): Future[T] ={
f.recoverWith { case _ if retries > 0 => after[T](delay,s)(retryWithFuture[T]( f , retries - 1 , delay)) }
}