Futures enable an efficient way to write parallel operations in a nonblocking IO fashion. Futures are placeholder objects for values that might not exist yet. Futures are composable, and they work with callbacks instead of traditional blocking code.
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> import concurrent.Future import concurrent.Future scala> import concurrent.ExecutionContext.Implicits.global import concurrent.ExecutionContext.Implicits.global scala> scala> val f: Future[String] = Future { "Hello world!" } f: scala.concurrent.Future[String] = Success(Hello world!) scala> scala> println("Result: " + f.value.get.get) Result: Hello world! scala> scala> println("Result: " + f) Result: Success(Hello world!) scala>
In order to work with futures in Scala, we have to import concurrent.Future
. We also need an executor, which is a way to work with threads. Scala has a default set of execution services. You can tweak it if you like, however, for now we can just use the defaults; to do that, we just import concurrent.ExecutionContext.Implicits.global
.
It's possible to retrieve the Future
value. Scala has a very explicit API, which makes the developer's life easier, and also gives good samples for how we should code our own APIs. Future has a method called value
, which returns Option[scala.util.Try[A]]
where A
is the generic type you are using for the future; for our case, it's a String A
. Try
is a different way to do a try...catch, and this is safer, because the caller knows beforehand that the code they are calling may fail. Try[Optional]
means that Scala will try to run some code and the code may fail -- even if it does not fail, you might receive None
or Some
. This type of system makes everybody's lives better, because you can have Some
or None
as the Option return. Futures are a kind of callback. For our previous sample code, the result was obtained quite quickly, however, we often use futures to call external APIs, REST services, Microservices, SOAP Webservices, or any code that takes time to run and might not get completed. Futures also work with Pattern Matcher. Let's see another sample code.
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> import concurrent.Future import concurrent.Future scala> import concurrent.ExecutionContext.Implicits.global import concurrent.ExecutionContext.Implicits.global scala> import scala.util.{Success, Failure} import scala.util.{Success, Failure} scala> def createFuture():Future[Int] = { | Future { | val r = scala.util.Random | if (r.nextInt(100)%2==0) 0 else throw new RuntimeException("ODD numbers are not good here :( ") | } | } createFuture: ()scala.concurrent.Future[Int] scala> def evaluateFuture(f:Future[_]) { | f.onComplete { | case Success(i) => println(s"A Success $i ") | case Failure(e) => println(s"Something went wrong. Ex: ${e.getMessage}") | } | } evaluateFuture: (f: scala.concurrent.Future[_])Unit scala> evaluateFuture(createFuture) scala> Something went wrong. Ex: ODD numbers are not good here :( evaluateFuture(createFuture) A Success 0 scala> evaluateFuture(createFuture) Something went wrong. Ex: ODD numbers are not good here :( scala> evaluateFuture(createFuture) Something went wrong. Ex: ODD numbers are not good here :( scala> evaluateFuture(createFuture) A Success 0 scala> evaluateFuture(createFuture) A Success 0 scala>
There is a function called createFuture
, which creates Future[Int]
each time you call it. In the preceding code, we use scala.util.Random
to generate random numbers between 0 and 99. If the number is even, we return a 0
, which means success. However, if the number is odd, we return a RuntimeException
, which will mean a failure.
There is a second function called evaluateFuture
, which receives any Future. We allow a result of any kind of generic parameterized type of function, because we used the magic underscore _
. Then we apply Pattern Matcher with two case classes: Success
and Failure
. In both the cases, we just print on stdin
. We also use another interesting and handy Scala feature called String interpolation. We need to we start the String with s
before ""
. This allows us to use expressions with $
and ${}
to evaluate any variable in the context. This is a different approach for String concatenation from what we have done so far. Later, we made 6
calls for the evaluteFuture
function, passing a new Future each time, created by the function createFuture
.