I'm curious about the best way to recursively build a chain of Akka futures which will run sequentially, if a doWork
call in a future fails, the future should be retried up to 3 times, the chain should fail if it runs out of retry attempts. Assuming all doWork
calls pass the returned future futChain
should only complete.
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
}
}
- How can I get the results as a collection? i.e. in this example each
String
return from thedoWork
function (I need to somehow modify therecurse
func to return aFutrue[List[String]]
- Should I use
recover
orrecoverWith
? - Is it ok to call
flatMap
to chain these calls - Should I make considerations about tail recursion & stack overflows?
- Would I be better to recursively build a list of futures and reduce them?
You can implement a retryable
Future
like this:This isn't optimized for tail recursion, but if you only intend on retrying a few times, you won't get a stack overflow (and I imagine if it's failed the first few, it's going to keep failing, anyway).
Then I would do the chaining separately. If you have a finite number of functions to chain together, each depending on the previous (and for some reason you want to aggregate the results) you can use
for
comprehensions (syntactic sugar forflatMap
):For an arbitrarily long chains, you can do them in parallel using
Future.sequence
(Futures
in the Akka library):This will unravel what would otherwise be
List[Future[String]]
toFuture[List[String]]
.Here's one way to do a similar thing in sequence:
The implementation of these functions is very sensitive to your use case. The two above functions will return failed futures if any of the futures in the chain failed. Sometimes you'll want this, other times not. If you want to collect only the successful futures, and discard failed ones without failing the entire result, you can add an extra step to recover the failures.
Additionally, the difference between
recover
andrecoverWith
is the type ofPartialFunction
it accepts.recover
replaces failed futures with default values, whilerecoverWith
does so using anotherFuture
. In the case of myretry
,recoverWith
is more appropriate because I'm trying to recover the failedFuture
with itself.