Lgd. Viktor Klang

Systems all the way down

Viktor Klang bio photo


This is the third of several posts describing protips for scala.concurrent.Future. For the previous post, click here.

Unapplying Future.apply (sometimes)

I quite frequently encounter code which looks a bit like this:

import scala.concurrent._
def doSomething(someParam: SomeType)(implicit ec: ExecutionContext): Future[ResultType] =
  if (someParam == SomeValue) Future(SomeConstant)
  else Future(performSomeCalculation())

So what’s wrong with that? From a correctness point-of-view: nothing. However, if performance is a part of correctness—or at the very least part of availability—then understanding the implications, and alternatives, might be interesting!

Let’s start with: what does Future.apply do?

According to its ScalaDoc, it does the following:

Starts an asynchronous computation and returns a Future instance with the result of that computation.

Alright, so it “starts a computation”. Furthermore the scaladoc says:

The following expressions are equivalent: val f1 = Future(expr) val f2 = Future.unit.map(_ => expr)` The result becomes available once the asynchronous computation is completed.

Interesting! So whenever you write Future(something) it is actually equivalent to having written Future.unit.map(_ => something).

So the problem with the code is that we’re—needlessly—starting an asynchronous computation in order to create a Future with an already known value.

What can we do instead of Future.apply?

The Future companion object sports 3 additional types of “constructors” for values of Future type:

  • successful
    • Creates an already completed Future with the specified result.
  • failed
    • Creates an already completed Future with the specified exception.
  • fromTry
    • Creates an already completed Future with the specified result or exception.

Armed with this knowledge, we can rewrite the example as follows:

import scala.concurrent._
def doSomething(someParam: SomeType)(implicit ec: ExecutionContext): Future[ResultType] =
  if (someParam == SomeValue) Future.successful(SomeConstant)
  else Future(performSomeCalculation())

This avoids starting an asynchronous computation in the case where someParam == SomeValue.

TL;DR:

Prefer Future.successful, Future.failed, and Future.fromTry when you need to create an instance of Future and you already have the value.

Click here for the next part in this blog series.

Cheers,