Scala Futures: simple Explanation with Examples

Scala Futures: simple Explanation with Examples

Last updated:
Table of Contents

Updated for Scala 2.12.11

Futures 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 Futures:

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!" 

Dialogue & Discussion