Futures are a powerful tool that has been developed by the Akka team and then adopted as a standard Scala library from version 2.10.
A Future is a placeholder for a value that will be available in the future: thanks to it, it is possible to run operations in parallel and to worry about what to do with it only once the value is available making our applications more scalable and performant. A lot can be achieved with it, have a look at the official Scala documentation for Future and Promises. Each future can be seen as an isolated parallel operation, so combining them can be challenging: in this article we will describe how Futures can be composed together.
How to Select the Fastest Future
Let’s assume that in our application we have more services to perform the same operation and that these services have a different response time according to their traffic load. Because our application doesn’t have any information on the load of each service, or simply we don’t want to rely on it, we want to call all the services and get the first reply we get back: let’s see how this can be achieved using futures.
First of all, let’s simplify our life a bit: for the purposes of this tutorial, we will simulate the behaviour of our services with a method that will wait a period of time before returning a String wrapped in a Future:
def reply(timeout: Duration, msg: String): Future[String] = Future { Thread.sleep(timeout.toMillis) msg }
Future.firstCompletedOf is the function that we are looking for: it will get a sequence of futures and return the first one that completes:
val futureSlowReply = reply(1 second, "Hello from a slow fella") val futureFastReply = reply(100 milliseconds, "I am a super fast fella!") val futureReplies = Seq(futureSlowReply, futureFastReply) val futureFastestReply = Future.firstCompletedOf(futureReplies) Await.result(futureFastestReply, 100 milliseconds) // res0: String = I am a super fast fella!
Note that waiting 100 milliseconds to complete the future is enough: all the futures are run in parallel and we know that the fastest will complete by then.
How to Combine Futures in Parallel
What if we have different services that process that same information differently? For example, given a customer id we have a service to retrieve the account information, another to retrieve the payment details, another to retrieve product suggestions based on previous selections. We could do it the old Java style way and retrieve sequentially all the information…or we could retrieve all the information in parallel and be really efficient! 😀
Let’s see how this can be achieved using the zip method of the Future class:
val futureSlowReply = reply(1 second, "Hello from a slow fella") val futureFastReply = reply(100 milliseconds, "I am a super fast fella!") val futureAllParallelReplies< = futureSlowReply.zip(futureFastReply) Await.result(futureAllParallelReplies, 1 second) // res1: (String, String) = (Hello from a slow fella,I am a super fast fella!)
Note that waiting the combined future value, called futureAllParallelReplies, for less than 1 second would generate a java.util.concurrent.TimeoutException: the zip function needs all the futures to be completed before returning a composition of all the futures!
How to Concatenate Futures
In order to combine futures in parallel they need to be independent from each other. What if this is not possible and we need to run them sequentially?
All we need to do is using the for-comprehension loop to force the futures to run sequentially:
def futureAllSequentialReplies(msg: String) = for { firstReply <- reply(100 milliseconds, msg) nextMsg = if (msg.length < 3) msg.reverse else msg.toUpperCase secondReply <- reply(200 milliseconds, nextMsg) } yield (firstReply, secondReply) Await.result(futureAllSequentialReplies("Hi"), 400 milliseconds) // res2: (String, String) = (Hi,iH) Await.result(futureAllSequentialReplies("Hello"), 400 milliseconds) // res3: (String, String) = (Hello,HELLO)
Note that waiting for 300 milliseconds is not enough: not only the futures are run sequentially moreover, but also we spend some time computing the nextMsg String.
Summary
Future is a powerful tool to perform operations in parallel. However, combining several parallel operation can be challenging. This article has described who easily we can compose Scala Futures: how to filter them, how to combine them in parallel and, when needed, how to force them to run sequentially.