A tour to the Scala Futures

Reading Time: 6 minutes

While executing long computations, performance is something that’s always being the concern. Luckily, Futures come to our rescue. A Future gives you a simple way to run an algorithm concurrently. Futures are the standard mechanism for writing multithreaded code in Scala. Whenever we create a new Future operation, Scala spawns a new thread to run that Future’s code, and after completion, it executes any provided callbacks.

So, Future comprises of these four characteristics:

     EFFICIENT  |  NON-BLOCKING | PARALLEL | ASYNCHRONOUS

A Future is a placeholder object for a value that may not yet exist. Generally, the value of the Future is supplied concurrently and can subsequently be used. Composing concurrent tasks in this way tends to result in faster, asynchronous, non-blocking parallel code.

A sample Future block would look like:

Screenshot from 2020-04-18 22-00-50

Execution Context

Execution meaning executing/doing something and Context meaning the setting/place for an event. So, EC can be thought of as an executor which is responsible for carrying out the computation in a new thread, in a pooled thread or in the current thread.

Now let me explain to you the imports which we saw in the above example:

  1. import scala.concurrent.{ ExecutionContext, Future }
    In order to use Future and ExecutionContext (its need just explained above), we need to import them first.
    scala.concurrent is a package comes with a lot of features including ExecutionContext, Future. But one can create their own execution context by simply extending the trait ExecutionContext.
  2. import scala.concurrent.ExecutionContext.Implicits.global
    ExecutionContext contains factory methods for creating execution contexts and one of them is Global. You can import global when you want to provide the global ExecutionContext implicitly.
    The default ExecutionContext implementation is backed by a work-stealing thread pool. By default, the thread pool uses a target number of worker threads equal to the number of available processors.

Futures

As written in the definition above, the Future is a block of computation that could be completed or not completed. A future that gets completed with a value, we say Future is successfully Completed. A future that gets completed with an exception thrown by the computation, we say Future is Failed with an exception.

Future is immutable: Future value may only be assigned once. Once a Future object is given a value or an exception, it becomes in effect immutable– it can never be overwritten.

Future.apply: It is a factory method to create a future object to carry out the computation asynchronously and returns the future holding the result of the computation once the future gets completed.

Future[T]: It is a type which denotes future objects

Callbacks

Once the Future is ready with the result of the computation (success or failure), we want to process or simply get that result and Callbacks help us in doing this. They are

  • onComplete: It is the most commonly used callback method that takes a partial function, in which you should handle the Success and Failure cases, like this:
Screenshot from 2020-04-19 20-18-02
  • onSuccess: When this future is completed successfully (i.e., with a value),
    apply the provided partial function to the value if the partial function
    is defined at that value. If the future has already been completed with a value,
    this will either be applied immediately or be scheduled asynchronously.
  • onFailure: When this future is completed with a failure (i.e., with a throwable), apply the provided callback to the throwable. It will not be called in case the future is completed with a value.

onSuccess and onFailure return partial functions, so we still need to use pattern matching to extract their contents.
Note that these methods (onSuccess and onFailure) have been deprecated.

The onComplete method has the result type Unit, which means the invocation of this method cannot be chained. Note that this design is intentional, to avoid suggesting that chained invocations may imply an ordering on the execution of the registered callbacks (callbacks registered on the same future are unordered).

Combinators

Sometimes we need to chain multiple Future together. Our result of one Future needs to feed to another Future computation and so on. So, clearly, onComplete is not going to help us in that. We have combination functions to chain up the futures. The combinators act on a Future value and return the corresponding new Future.

Flatmap, map, filter, recover, recoverWith, fallBackTo are some of the examples of the combinator.

One thing to note is that the combinators are internally implemented using callbacks. Combinators are used when we want the result of a Future variable/constant and perform computations on it and return the result of the computation as a new future.

map

Screenshot from 2020-04-19 20-53-31

This shows Future(), but if you check incrementedSalary’s value, you’ll see that it contains the expected result:

Screenshot from 2020-04-19 20-55-09

But what if the evaluation of the first Future computation results in an exception?

We should be prepared for that as well. We can handle this scenario with the help of recover and recoverWith method.

recover requires a partial function which matches an exception and returns the default value whereas recoverWith is used to match an exception and substitute it with another Failure.
Let’s suppose we have two gateways to calculate the salary of an employee. 

Screenshot from 2020-04-20 13-24-33

And our intention is to call the other gateway to compute the salary if the first gateway fails for some reason. Let’s understand how recover and recoverWith help us in this scenario:

Screenshot from 2020-04-20 13-29-43

Screenshot from 2020-04-20 13-35-27

We can observe the return type of both.

Let’s consider the scenario where both the gateways are unresponsive. 

Screenshot from 2020-04-20 13-37-47

Screenshot from 2020-04-20 13-38-38

The for expression

Combinators are a very efficient mechanism to access and use the value of a Future but they have a limitation i.e. it becomes a bit complex to use combinators when we have to use several futures to compute a result. For example, we have to add two future values and return a new future as their sum. For comprehensive is used for situations like this when we have to use multiple future values to compute the result.

Screenshot from 2020-04-19 23-35-13

Here, creditedSalary future is completed only when ‘salary’ and ‘bonus’ futures are completed. The ‘creditedSalary’ guarantees that the final future value will always be a Success and will have the default value of 0 in case of Failure of ‘creditedSalary’.

Blocking outside the Future

Callbacks and combinators on futures are a preferred way to use their results. However, blocking may be necessary for certain situations and is supported by the Futures and Promises API.

scala.concurrent provides Await object for blocking the current thread until a Future completes, but its use is highly discouraged as it limits the efficiency advantages of multithreading, prevents freeing of system resources, and introduces the possibility of deadlock.

Await has two methods:

1) ready: It waits for the “completed” state of an Awaitable(Future[T]).
2). result: It waits and returns the result (of type T) of an Awaitable(Future[T]).

Both are blocking for at most the given Duration. However, Await.result tries to return the future result right away and throws an exception if the future failed while Await.ready returns the completed future from which the result (Success or Failure) can safely be extracted via the value property.

Screenshot from 2020-04-19 23-56-36

Alternatively, calling Await.ready waits until the future becomes completed, but does not retrieve its result. In the same way, calling that method will not throw an exception if the future is failed.

Screenshot from 2020-04-20 13-18-48

That was a quick guide to use Futures in Scala. Hope it helped you! 🙂

References:

Knoldus-blog-footer-image

Written by 

Ramandeep Kaur is a Software Consultant, having experience of more than 1.5 years. She is a Java enthusiast and has knowledge of languages like C, C++, C#, and Scala. She is familiar with Object Oriented Programming Paradigms and also has a great interest in relational database technologies. Her hobbies include reading novels and listening music.