Scala Future gives you a simple way to run an algorithm concurrently.
Scala Future: Run and Block
By following this approach, first create a future and then block to wait for its result.
import scala.concurrent.{Await, Future}
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
object ScalaFuture extends App {
//creating future
val f = Future {
sleep(500)
"Hello" + "Vipul"
}
val response = Await.result(f, 1 second) // Blocking
println(response)
sleep(1000)
}
Future and ExecutionContext
A Future[T] is a container that runs a computation concurrently, and at some future time may return either (a) a result of type T or (b) an exception.
The result of the computation becomes available once the future completes.
- An
ExecutionContext
executes a task it’s given. You can think of it as being like a thread pool. - The
ExecutionContext.Implicits.global
import statement shown in the examples imports the default global execution context.
When we are using Future we must use ExecutionContext.global, making it available using a single import:
import scala.concurrent.ExecutionContext.Implicits.global
The first method for working with Future
functionally is map
.
If we want to demonstrate Future in simple way then we can generate a Future Response in given way:
import java.util.UUID
def getRandomUUID() : UUID = {
Thread.sleep(3000L)
UUID.randomUUID()
}
val generatedUUID: Future[UUID] = Future {
getRandomUUID()
}
When we call Future.apply with the call to getRandomUUID inside, the Future runtime executes it on another thread. It might look like we were passing the result of invoking the method to the Future, however, Future.apply takes it as a by-name parameter. It moves evaluation into a thread provided by implicit ExecutionContext.
CallBack
onComplete():
val results: Future[List[Tag]] = Tags.all.toList
results onComplete {
case Success(list) => //do something with list
case Failure(t) => //throw the error
}
onComplete
returns Unit
, it allows you to do something with the Future
result, but it won’t return a value.
Transform Future
map:
def map[B](f: A => B): Option[B]
When we have a Future instance, we can use the map method to transform its successful result without blocking the main thread:
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
val numF = Future{ 3 }
val stringF = numF.map(n => Future(n.toString))
flatMap:
def flatMap[B](f: A => Option[B]): Option[B]
flatMap
allow you do something with the list and return a new Future
.
val finalFuture = results
.flatMap(x => /* do something and return a future */)
.flatMap(y => /* do something else and return a future */)
.flatMap(z => /* do something else and return a future */)
.map(myresult => /* do something */)
If something goes wrong along the way, the chain is broken early and you’ll get the first error occurred.
This allows for an even nicer syntax
val finalFuture = for {
x <- results
y <- /* do something and return a future */
z <- /* do something and return a future */
} yield something(z)
transform:
As opposed to the map() function, we can map both successful and failed cases with the transform(f: Try[T] => Try[S]) function:
val future = ... // Future[T]
val mapped = future.transform {
case Success(_) => Success("OK")
case Failure(_) => Success("KO")
}
Combine futures
zip:
def futureA = Future { "A" }
def futureB = Future { "B" }
def futureC = Future { "C" }
f1.zip(f2).zip(f3).map{
case ((f11,f22),f33) => (f11,f22,f33)
}
lazyZip:
If we want to combine the results of two independent variables into one, then we should use the lazyZip method:
val listOne = List(1, 2, 3, 4, 5)
val
list
Two = List(2, 3, 4, 5, 6)
val sumOf = (listOne
lazyZip
).map((m, o) => m+o)list
Two
print(fruits)
Results => List(3,5,7,9,11)
Conclusion
Here we discussed Future API, and how we can use Future and available functions.
