Scala Futures by example
What is a Future?
A Future is essentially a placeholder object that is created for a result that does not yet exist. Scala Futures are asynchronous and non-blocking (by default) and are thus often handled with callbacks.
Hmm. What about ‘callback hell’?
Scala Futures are monadic in nature and can be combined, composed, sequenced, and executed concurrently. All of this can be accomplished with Scala in a very simple, yet highly composable manner. In this article, we will see this in action.
Let’s now explore the following scenarios:
- Create/Execute a single Future to perform some computation asynchronously
- Create/Execute multiple asynchronous computations sequentially
- Create/Execute multiple asynchronous computations
- Execute multiple callbacks sequentially
Before we begin, to handling a Scala Future
, we will require an ExecutionContext
.
For the purpose of this blog we will always use global
ExecutionContext
.
To do so, simply import the following implicit:
[code language=”Scala”]
import ExecutionContext.Implicits.global
[/code]
Note: Executions contexts are beyond the scope of this blog post.
Create a single Future to perform some computation asynchronously
In the example below, we first create a new Future
that will concatenate a string. In order to handle this future’s result, we the register a callback using the Future
‘s onSuccess
method. onSuccess
takes a partial function which defines the callback and contains our side-effecting code. In this case, onSuccess will simply print the concatenated string.
Note that onSuccess
will be called asynchronously once the Future
is completed. If the Future
has completed prior to our call to onSuccess
, then onSuccess
will be invoked immediately or scheduled asynchronously.
[code language=”Scala”]
// Create a future
val future = Future {
"My " + "First " + "Future."
}
// Print the result
future onSuccess {
case _ => println _
}
[/code]
Note: This code does not handle faliure. To do so, one can similarly use onFailure
or onComplete
.
Execute multiple asynchronous computations sequentially
With the previous example fresh in our minds, let’s take a moment to consider how one might execute a series of futures in sequence. A first attempt, might be to simply create each subsequent Future
within the previous Future
s’ onSuccess
callback — effectively nesting futures. Although, such a solution does work, it can be messy and it quickly leads us into the dreaded realm of callback hell! Noooooooo!
Fortunately, Scala provides a far better option. Scala Future
s are monadic and provide map
, flatMap
, and filter
functions! Because Future
s are monads they can be used with Scala’s for comprehensions. Scala for comprehensions are incredibly powerful and capable of taking complex code and transforming into something far more simple and readable.
The code snippet below describes an example of this power. This code creates three Future
s within a for comprehension. The first future, a
, returns the value 10
. The second future, b
, takes the result of a
as input and returns a new result, a * 2
. The third future, c
, again takes the result of a
as input and it returns, a + 2
. Finally, the for comprehension yields a new future which will hold the result c
.
The result of resFuture
using the side-effecting foreach
method. The result printed is 12
.
[code language=”Scala”]
val resFuture = for {
a <- Future(10)
b <- Future(a * 2)
c <- Future(a + 2)
if c > 10 // you can filter too!
} yield c
resFuture foreach println _
[/code]
Note the elegant structure of the above code. By using a for comprehension, we do not have to write an ugly nested series of callbacks.
The above code also exhibits a very important characteristic. That is, each Future
is run after the previous has completed. This is useful at times, but in this particular case, both b
and c
could execute concurrently. Let’s see how to do that in the next example.
Execute multiple asynchronous computations concurrently
In this example, we will extend the previous example to ensure b
and c
are executed concurrently, yet still executed after its dependency on a
.
[code language=”Scala”]
val fa = Future { 10 }
// execute fa first
val future = fa.flatMap { a =>
// execute fb and fc concurrently
val fb = Future(a * 2)
val fc = Future(a + 2)
for {
b <- fb
c <- fc
if c > 10
} yield c
}
future foreach println _
[/code]
Executing futures concurrently within a for comprehension is super easy. Simply create the futures first (see fb
and fc
. The for comprehension’s yield
will execute once all futures complete. That’s it!
Execute multiple callbacks in order after some asynchronous computation
Here we will describe how to execute multiple callbacks in a specified order upon a Future
‘s completion. Wait?!?! didn’t we already do this in our second example. Sort of. In the second example, we executed multiple Futures in sequence, then had the opportunity to perform some work once they had all completed. In this example, we will invoke multiple callbacks in order after a single Future completes. To do this, we will use andThen
[code language=”Scala”]
val resFuture = Future { /* eat pancakes */ } andThen {
case Failure(e) => // vomit
} andThen {
case _ => // smile happily
}
resFuture foreach println _
[/code]
In this example we guarantee that // smile happily
always occurs after /* eat pancakes */
succeeds or // vomit
. Note, that if one of the chained andThen
callbacks throws an exception, that exception is not propragated to the subsequent callbacks. Instead, the subsequent andThen
callbacks are given the original value of the future.