Future
Scala offers an easy way to manage parallel code execution in the form of Future
s and callbacks. Future is
a special object that is used to hold a value (usually a result of some computations) that may become available in the
future. Callback if method that will be run when Future
is completed. To use Future
s in our
application, first we need to import the scala.concurrent
package and we need to provideExecutionContext
. If you’re working with a Play-framework application, the easiest way to obtain execution
context is to use Play’s default thread pool. You can access it by @Inject()(implicit ec:
ExecutionContext).
To execute a block of code concurrently we need to call the Future.apply
method – as shown in the example
below.
val futureValue: Future[TypeA] = Future {
someCode() // Some method that returns value of type TypeA
}
Callbacks
The Future.apply
method returns the Future
object containing the return value of functionsomeCode()
. To access it we need to add some callback. There are few possibilities here:
onComplete
foreach
map
The first one – onComplete
– gives us access to two cases:
Success[T]
– which holds the return value of successfully completedFuture
Failed[T]
– which hold the value of thrown exception on failedFuture
futureValue.onComplete {
case Success(value) => someCode(value) // Some computation performed on value with type TypeA
case Failure(exception) => println("Error: " + exception.getMessage)
}
Second and third option allows us to handle only a successful result. Both onComplete
andforeach
have a result type of Unit
which prevents us from chaining them with other callbacks.
They are mostly used for side effects.
The last option – map
– gives us access to map results to any type and returns it in a new Future after
completion of the original future.
val futureValue: Future[TypeA] = Future {
someCode() // Some method that return value of type TypeA
}
val futureValue2: Future[TypeB] = futureValue.map{ value =>
someOtherMethod(value) // Some other method of type TypeA => TypeB
}
Parallel execution
Sometimes we need to access values of two or more different Future
s. One way of dealing with that problem
is nesting a callback of one future in another callback – as shown in the example below.
val future1: Future[TypeA] = Future{
someCode()
}
val future2: Future[TypeB] = Future{
someOtherCode()
}
val future3: Future[Future[TypeC]] = future1.map{ value1 =>
future2.map{ value2 =>
someMethodHere(value1, value2) // method of type (TypeA, TypeB) => TypeC
} // returns Future[TypeC]
}
As we can see, this isn’t the cleanest approach. More importantly, the return type of our nested map
s will
be nested Future
which will make accessing its result much harder. For that reason Future
haveflatMap
and withFilter
combinators that allow it to be used in for-comprehensions. The same
code as above, but with for-comprehension would look like this:
val future1: Future[TypeA] = Future{
someCode()
}
val future2: Future[TypeB] = Future{
someOtherCode()
}
val future: Future[TypeC] = for {
value1 <- future1
value2 <- future2
} yield {
someMethodHere(value1, value2) // method of type (TypeA, TypeB) => TypeC
}
When Future is executed?
While using Future
with either of callbacks, for-comprehension or other combinators, it’s very important to
make sure to use it correctly. If we compare two blocks of code:
// Block 1
val future1: Future[TypeA] = Future{
someCode()
}
val future2: Future[TypeB] = Future{
someOtherCode()
}
val future = for {
value1 <- future1
value2 <- future2
} yield {
...
}
// Block 2
val future = for {
value1 <- Future{someCode()}
value2 <- Future{someOtherCode()}
} yield {
...
}
On the first sight it may seem like they both do the same thing: run two methods parallel to each other. In reality only
the first one will run concurrently. Since for-comprehensions are extended to (sometimes) nested foreach
,map
, flatMap
and withFilter
the second block would result in running code similar
to this
Future{someCode()}.flatMap{value1 => Future{someOtherCode()}.map{value2 => ... }}
Which means that someCode()
will run first, and after its completion someOtherCode()
will be
started. Scala starts execution of code in Future.apply
when the instance is created, not when it’s
evaluated. By assigning future to value outside of for-comprehensions, we start both of them and then assign callback
methods to them in for-comprehensions.