Scala Future in Depth

Lavlesh Singh
4 min readAug 28, 2021

Happy weekend folks !!

With increasing number of computation power, the parallel computing gained the popularity during the last years. Java’s concurrent package is one of the proofs for that. But Scala, even though it’s able to work with Java’s concurrent features, comes also with its own mechanisms. Futures are one of them.

One consequence of the proliferation of multicore processors has been an increased interest in concurrency. Java provides concurrency support built around shared memory and locking. Although this support is sufficient, this approach turns out to be quite difficult to get right in practice. Scala’s standard library offers an alternative that avoids these difficulties by focusing on asynchronous transformations of immutable state: the Future.

Although Java also offers a Future, it is very different from Scala’s. Both represent the result of an asynchronous computation, but Java’s Future requires that you access the result via a blocking get method. Although you can call isDone to find out if a Java Future has completed before calling get, thereby avoiding any blocking, you must wait until the Java. Future has completed before proceeding with any computation that uses the result.

Photo by Bud Helisson on Unsplash

Introduction to Futures.

As in many other languages, Scala Future is a placeholder representing a value that will be computed soon. It’s immutable — once computed it can’t be overridden — and typed. The type represents the value returned by the Future. A Future can be terminated or not and the termination is either successful or in failure. And to make a Future work, we must defined an ExecutionContext . Most often importing import scala.concurrent.ExecutionContext.Implicits.global should be enough.
We can construct it with one of its convenient methods.

Future.apply({
“XYZ”
})

Future.successful(“ababab”)
Future.failed(new RuntimeException(“Test error”))

The first line represents a Future those result we don’t know. The second one is used to return a Future executed correctly while the last for a failed Future. The computation defined in apply(…) method executes in asynchronous, thus non-blocking, way. Many different ways exist to get the computation result.
One of the simplest one is the use of onComplete callback.

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.

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)

Using Map with Future

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

Using flatmap with Future

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

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

Thanks for Reading this blog !!

https://medium.com/analytics-vidhya/java-vs-scala-7ff9eb50141

--

--