I have a function that looks for an Elephant
over a network, returning a Future[Option[Elephant]]
. The return value is a Future
so that the function can return immediately while the network call happens asynchronously. It contains an Option
for which None
means not yet available, while Some
means the elephant has been found:
def checkForElephant : Future[Option[Elephant]] = ???
What I'd like to do is write a function called pollForElephant
.
def pollForElephant : Future[Elephant] = ???
This function should return a Future
over a process that will call checkForElephant
and succeed quickly if an element is found on the first check, but thereafter to check again every 10 seconds until an Elephant
is found, even if there are no elephants and it has to try forever.
The easy way to do this is just to force the check to be synchronous, write a recursive function outside of the Future
domain to poll, and then create a Future
over the whole affair:
import scala.annotation.tailrec
import scala.concurrent.{Await,Future,blocking}
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
class Elephant;
def checkForElephant : Future[Option[Elephant]] = ???
def synchronousCheckForElephant : Option[Elephant] = blocking {
Await.result( checkForElephant, Duration.Inf )
}
@tailrec
def poll( last : Option[Elephant] ) : Elephant = {
last match {
case Some( elephant ) => elephant
case None => {
blocking {
Thread.sleep( 10.seconds.toMillis )
}
poll( synchronousCheckForElephant )
}
}
}
def pollForElephant : Future[Elephant] = Future {
poll( synchronousCheckForElephant )
}
This seems terribly inelegant, to start from the Future
domain, force into synchrony, and then go back. I thought I should be able to do everything from the Future
. So, I tried this:
import scala.annotation.tailrec
import scala.concurrent.{Await,Future,blocking}
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
class Elephant;
def checkForElephant : Future[Option[Elephant]] = ???
// oops! this is not @tailrec
def poll( last : Future[Option[Elephant]] ) : Future[Elephant] = {
last.flatMap { mbElephant =>
mbElephant match {
case Some( elephant ) => Future.successful( elephant )
case None => {
blocking {
Thread.sleep( 10.seconds.toMillis )
}
poll( checkForElephant )
}
}
}
}
def pollForElephant : Future[Elephant] = poll( checkForElephant )
Unfortunately, as the comment above says, the poll(...)
function is not tail recursive. Elephants might take a long time to arrive, and I'm supposed to wait indefinitely, but the stack might blow.
And the whole thing feels a little weird. Should I just revert to the easier-to-reason-about synchronous approach? Is there a safe way to do what I mean to while staying in the Future
?