Scala Futures: simple Explanation with Examples
Last updated:- Run code in Future block
- Return Future from function
- Wait for future
- map
- flatmap
- for-comprehensions
- Catch errors
Updated for Scala 2.12.11
Future
s are a pattern for concurrent code execution present in many languages (Java, Scala, Clojure to name a few).
Here are a couple examples to show you how to use futures in scala code.
All code can be found on this jupyter notebook
Run code in Future block
printSlow
is a function that takes a long time and you don't want to wait for it. Wrap it in a Future{}
block to have it run in another thread, async:
import scala.concurrent._
import scala.concurrent.ExecutionContext.Implicits.global
def printSlow(name: String): String = {
Thread.sleep(1000) // do nothing for 1 second
name + " returned!"
}
def printFast(name: String): String = {
name + " returned!"
}
Future{ printSlow("first!") }
printFast("second!")
// >>> "second! returned!""
// >>> "first! returned!"
Note that the output of printFast
is printed before the output of the first function!
Return Future from function
In Scala,
Future
is both a keyword and a type, meaning you use it both in defining a future block (Future{} and in type signatures (Future[_]))
Instead of wrapping a function with a Future{}
block in the caller, you can embed the async nature into the function itself, and change it's type to Future[String]
instead, so that it natively returns a Future
value
import scala.concurrent._
import scala.concurrent.ExecutionContext.Implicits.global
def printSlowFuture(name: String): Future[String] = {
Future{
Thread.sleep(1000) // do nothing for 1 second
name + " returned!"
}
}
def printFast(name: String): String = {
name + " returned!"
}
// no need to wrap here, becauce the function will return a Future on its own
printSlowFuture("first!")
printFast("second!")
// >>> res1: String = "second! returned!""
// >>> res2: String = "first! returned!"
Wait for future
If you wait less time than the future takes to complete, you get a
java.util.concurrent.TimeoutException
To block the code until a future has been evaluated (i.e. blocking wait) just use Await.result()
and pass how long you are willing to wait.
import scala.concurrent._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
def slowOperationFuture(name: String): Future[String] = {
Future{
Thread.sleep(1000)// do nothing for 1 second
name + " returned!"
}
}
// this will work because you are waiting enough time
Await.result(slowOperationFuture("foo"), 2.seconds)
// >>> res: String = "foo returned!"
// this will trigger an error becase you waited too little time
Await.result(slowOperationFuture("foo"), 500.milliseconds)
// java.util.concurrent.TimeoutException: Futures timed out after [500 milliseconds]
// scala.concurrent.impl.Promise$DefaultPromise.ready(Promise.scala:259)
// scala.concurrent.impl.Promise$DefaultPromise.result(Promise.scala:263)
// scala.concurrent.Await$.$anonfun$result$1(package.scala:220)
// scala.concurrent.BlockContext$DefaultBlockContext$.blockOn(BlockContext.scala:57)
// scala.concurrent.Await$.result(package.scala:146)
// ammonite.$sess.cmd66$Helper.<init>(cmd66.sc:5)
// ammonite.$sess.cmd66$.<init>(cmd66.sc:7)
// ammonite.$sess.cmd66$.<clinit>(cmd66.sc:-1)
map
With map
, you can execute a synchronous operation after a Future is complete, without blocking
import scala.concurrent._
import scala.concurrent.ExecutionContext.Implicits.global
def slowOperationFuture(name: String): Future[String] = {
Future{
Thread.sleep(1000)// do nothing for 1 second
name + " returned!"
}
}
// note that this is a synchronous (blocking) function
def makeTextUpperCaseFast(inputText: String): String = inputText.toUpperCase
slowOperationFuture("foo").map(
output => makeTextUpperCaseFast(output))
// >>> res: Future[String] = Success("FOO RETURNED!")
flatmap
As with
map
, the main thread is never blocked.
Use flatmap
to trigger another function that returns a Future
after the first is complete.
import scala.concurrent._
import scala.concurrent.ExecutionContext.Implicits.global
def slowOperationFuture(name: String): Future[String] = {
Future{
Thread.sleep(1000)// do nothing for 1 second
name + " returned!"
}
}
// note that this is an asynchronous (nonblocking) function
def makeTextUpperCaseSlowFuture(inputText: String): Future[String] = {
Future{
Thread.sleep(1500) // Do nothing for 1,5 seconds
inputText.toUpperCase
}
}
slowOperationFuture("foo")
.flatMap(output => makeTextUpperCaseSlowFuture(output))
// >>> res: Future[String] = Success("FOO RETURNED!")
for-comprehensions
For maximum performance in multithreaded systems, you want every slow function (e.g. disk or network access) to be a Future
so that you can use multiple threads efficiently.
But using map
and flatMap
is cumbersome when you want to chain several async methods.
To make this easier, you can use for comprehensions.
This is the template for for-comprehensions to evaluate Futures:
for {
result1 <- functionThatReturnsAFuture(foo)
result2 <- anotherFunctionThatReturnsAFuture(result1)
...
} yield final_result
Example: chain functions to build a string, make it uppercase, calculate the number of letters and multiply by two:
import scala.concurrent._
import scala.concurrent.ExecutionContext.Implicits.global
def slowOperationFuture(name: String): Future[String] = {
Future{
Thread.sleep(1000)// do nothing for 1 second
name + " returned!"
}
}
def makeTextUpperCaseSlowFuture(inputText: String): Future[String] = {
Future{
Thread.sleep(1500) // Do nothing for 1,5 seconds
inputText.toUpperCase
}
}
def countNumberOfLettersSlowFuture(inputText: String): Future[Int] = {
Future{
Thread.sleep(1000)
inputText.size
}
}
def multiplyByTwoFast(num: Int):Int = num * 2
// for comprehension here:
for {
result <- slowOperationFuture("foo")
uppercased <- makeTextUpperCaseSlowFuture(result)
numLetters <- countNumberOfLettersSlowFuture(uppercased)
} yield multiplyByTwoFast(numLetters) // this is a fast function so it is left outside
// >>> res: Future[Int] = Success(26)
Catch errors
It is possible that async functions may throw Exceptions in case of errors.
In this case, use recover
to handle errors in functions that return Future
s:
import scala.concurrent._
import scala.concurrent.ExecutionContext.Implicits.global
def functionThatThrowsErrorInFuture(inputText: String) = {
Future{
Thread.sleep(1000) // do nothing for 1 second
throw new Exception("BOOM!")
}
}
functionThatThrowsErrorInFuture("foo")}.map{
successfulResult => println(s"Success! ${successfulResult}")
}.recover{
case e:Exception => println(s"Error! ${e}")
}
// >>> res = "Error! java.lang.Exception: BOOM!"