You can't stop the future but you can always move to the fast lane
libraryDependencies += "com.softwaremill.undelay" %% "undelay" % "0.2.0"
Scala Futures provide a nice interface referencing a deferred value. This deferral will not make your slow operation run any faster. A slow operation scheduled in a Future will result in a future that will be slow to satisfy.
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
val longDivision = Future {
Thread.sleep(10.seconds.toMillis)
1 / 1
}
longDivision.onComplete {
case res => println(s"long div completed with $res")
}
An application that needs a response quickly can demand the future to respond quickly using Await with a timeout duration.
import scala.concurrent.Await
val result = Await.result(longDivision, 1.second)
There are a few drawbacks to this approach.
- you are giving up any result composability you get with Futures
- you are blocking the current thread until the future is satisfied
- you are now back to a world where throwing exceptions is the norm
Undelay, provides combinator which allows you do specify a maximum duration of time a future has to complete is operation.
import undelay._
val shortDivision = longDivision.within(1.second)
shortDivision.onComplete {
case res => println(s"should division completed with result of $res")
}
The undelay
package defines an implicit value class called Complete
which takes a single future argument. Complete instances may declare
a finite duration suitable to complete your task with the within(deadline)
method. By default, the future will be failed with a TimeoutException. You may optional provide your own exception defining function which takes the provided duration and returns a suitable Throwable. Calling within
will not block your current thread, nor cost you future compatibility, nor throw an exception in your current thread.
val recovery = shortDivsion.recover {
case e: TimeoutException => checkNeighborsAnswer()
}
recovery.onComplete {
case res => println(s"we got $res quickly without blocking and without interrupting the current thread")
}
The wildcard import above is there only for the aesthetic of making it look like you can call within
on a Scala future. If this is not your thing, you may
be more explicit in your travels.
val shortDivision = undelay.Complete(longDivision).within(1.second)
shortDivision.onComplete {
case res => println(s"should division completed with result of $res")
}
Doug Tangren (softprops) 2014