Mike Slinn

Futures & Promises

— Draft —

Published 2013-06-03. Last modified 2014-11-13.
Time to read: 4 minutes.

Composable futures are a powerful concurrency mechanism – the one you should consider first if you need non-blocking, asynchronous computations. This lecture features a tiny implementation of a WriteOnce storage class, Promise and Future.

This lecture, and most of the other lectures in this section, is updated and expanded material which was originally published in my book, Composable Futures with Akka 2.0. That book is now out of date; it only pertained to Scala 2.10.

The sample code for this lecture can be found in courseNotes/src/main/scala/multi/futures/TinyFuture.scala.

A Future is a container which will assume a value when a computation completes or a result becomes available at a future time. This lecture pertains to the Future class and companion object in the scala.concurrent package.

Scala Futures:

  • Manage multi-threading asynchronously (unlike Scala parallel collections, which are synchronous).
  • Are higher-level than j.u.c. primitives.
  • Are immutable.
  • Are inexpensive to create.
  • Can be preset to a value or exception when created.
  • Are non-blocking, however blocking operations are supported.
  • Are composable.

Methods that return a Future instead of blocking for a result provide fast, asynchronous, non-blocking code. These methods are called asynchronous.

A Future Can Start With a Promise

A Future[T] starts an asynchronous computation on another execution context and makes the result available to the original execution context once it has completed. Futures are writable once and readable many times.

They are usually created by passing in a closure (discussed in the Closures lecture of the Introduction to Scala course) or a Function (discussed in the Functions are First Class and More Fun With Functions lectures of the same course), so they usually start off without a value.

The two states of a Future are pending or completed. The value of a Future[T] might be of type T or it might be an Exception. This is possible because the internally, a Future’s value is stored in an instance of Try[T].

Futures are executed as soon as an execution context is available to perform the work. Normally when you hear “another execution context”, you think “another thread”, but there are situations where there is only one thread ever available, so for single-threaded programs like those produced by NodeJS, many execution contexts are interleaved on one thread. This is good when you do not have hardware support for multi-threading, but is quite inefficient otherwise. In many cases where multi-threading is enabled, a Future will start computing the value on another thread immediately, but you cannot assume this to always be the case, because another thread may not be available right away.

A Promise is a signaling mechanism which exists in the same execution context as it was created on. A Promise can be created complete with a value or exception, or it can be completed with a value or exception at later time. Unlike Futures, promises are fulfilled on demand. The value of a Promise is held by an internal Future. Internally, a Promise stores the value more or less as type Future[Try[T]]. We will explore the storage mechanism in more detail in a moment. This means that Promises also have two states, just like Futures: pending or completed.

When complete, a Promise might have a Success[T] value, or it might contain a Throwable.

  • Invoking Promise.complete(newValue) triggers a transition from state Pending to Completed while storing Success(newValue) as the value of the Promise.
  • Invoking Promise.failure(throwable) triggers a transition from state Pending to Completed while storing Failure(throwable) as the value of the Promise.

This might seem confusing, so let’s examine a simplified implementation of futures so we can understand how this really works. I only show you this code so you can see how Promise relates to Future, and how value storage works. Although TinyPromise and the write-once mechanism are reasonable facsimiles of how the real Scala implementation works, the TinyFuture implementation is so minimal that it could teach bad habits. Beware!

WriteOnce Immutable Type

One of the main concepts behind Scala futures is a write-once variable that can store one of two types of information: a parametric value or a Throwable. I’ve defined the WriteOnce class that is faithful to the documented behavior of Futures and Promises. The following code is provided in TinyFuture.scala and is defined in a TinyFuture object. Once you get familiar with this code, understanding Scala’s Futures and Promises will be easy.

If you need some help remembering what the exists combinator does, review the Combinators lecture which was presented earlier in this course.

Scala code
class WriteOnce[T] {
  private var value: Option[Try[T]] = None
def isCompleted = value.isDefined
def isFailure = value.exists(_.isFailure)
def isSuccess = value.exists(_.isSuccess)
/** Create an instance with a failure value (a Throwable instance) */ def fail(throwable: Throwable): Unit = { if (value.isEmpty) value = Some(Failure(throwable)) else throw new Exception(WriteOnce.alreadySetMSg) () }
/** Create an instance with a success value */ def set(newValue: T): T = { if (value.isEmpty) value = Some(Success(newValue)) else throw new Exception(WriteOnce.alreadySetMSg) newValue }
/** Get success value */ def success: T = value.map { case v if v.isSuccess => v.get case v => throw new Exception("WriteOnce is a failure, there is no value stored.") }.getOrElse(throw new Exception(WriteOnce.stillPendingMsg))
/** Get failure value (a Throwable instance) */ def failed: Throwable = value.map { case v if v.isFailure => v.failed.get case v => throw new Exception("WriteOnce is a success, there is no Throwable stored.") }.getOrElse(throw new Exception(WriteOnce.stillPendingMsg)) }
object WriteOnce { val stillPendingMsg = "WriteOnce is still pending, there is no value yet." val alreadySetMSg = "Value of WriteOnce is already set" }

We can create a WriteOnce instance in various ways and use them, like this:

Scala code
object WriteOnceDemo extends App {
  import TinyFuture.WriteOnce
val writeOnce1 = new WriteOnce[Int]() writeOnce1.set(42) println(s"writeOnce1.isCompleted=${writeOnce1.isCompleted}") println(s"writeOnce1.isFailure=${writeOnce1.isFailure}") println(s"writeOnce1.isSuccess=${writeOnce1.isSuccess}") println(s"writeOnce1.success=${writeOnce1.success}") try { writeOnce1.failed } catch { case exception: Throwable => println(s"writeOnce1.failed: ${exception.getMessage}") } println()
val writeOnce2 = new WriteOnce().set(420) println(s"writeOnce2.isCompleted=${writeOnce2.isCompleted}") println(s"writeOnce2.isFailure=${writeOnce2.isFailure}") println(s"writeOnce2.isSuccess=${writeOnce2.isSuccess}") println(s"writeOnce2.success=${writeOnce2.success}") try { writeOnce2.failed } catch { case exception: Throwable => println(s"writeOnce2.failed: ${exception.getMessage}") } println()
val writeOnce3 = new WriteOnce().fail(new Exception("You reached the end of the Internet")) println(s"writeOnce3.isCompleted=${writeOnce3.isCompleted}") println(s"writeOnce3.isFailure=${writeOnce3.isFailure}") println(s"writeOnce3.isSuccess=${writeOnce3.isSuccess}") try { writeOnce3.success } catch { case exception: Throwable => println(s"writeOnce3.success: ${exception.getMessage}") } println(s"writeOnce3.failed=${writeOnce3.failed}") }

You can run this by typing:

Shell
$ sbt "runMain multi.futures.WriteOnceDemo"
writeOnce1.isCompleted=true
writeOnce1.isFailure=false
writeOnce1.isSuccess=true
writeOnce1.success=42
writeOnce1.failed: WriteOnce is a success, there is no Throwable stored.
writeOnce2.isCompleted=true writeOnce2.isFailure=false writeOnce2.isSuccess=true writeOnce2.success=420 writeOnce2.failed: WriteOnce is a success, there is no Throwable stored.
writeOnce3.isCompleted=true writeOnce3.isFailure=true writeOnce3.isSuccess=false writeOnce3.success: WriteOnce is a failure, there is no value stored. writeOnce3.failed=java.lang.Exception: You reached the end of the Internet

TinyFuture Implementation

My tiny Future implementation is rather short and simple. Because it does not support concurrency it just shows how the WriteOnce behavior supports the Future.value getter.

Getting the value of this tiny Future implementation is very different than how you retrieve values from the real Scala Future implementation. Although this code is simple and easy to understand, it is a very naïve implementation and its only useful for showing how Futures work with WriteOnce values. I will show you how to extract values from real Scala Futures later.

Scala code
class Future[T] {
  val value = new WriteOnce[T]
def isCompleted = value.isCompleted }
object Future { def apply[T](value: => T) = { val future = new Future[T]() try { future.value.set(value) } catch { case e: Exception => future.value.fail(e) } future }
def failed[T](throwable: Throwable) = { val future = new Future[T] future.value.fail(throwable) future }
def successful[T](value: T): Future[T] = Future(value) }

The TinyFuture companion object has two methods that are just like two methods of the real Scala Future’s companion object of the same name: successful and failed. These methods create completed Futures from the values passed to them; no extra threads or context switch is required. These methods are useful when you need to supply a Future containing a value that does not require computation as an argument to a method call, or for use in a for-comprehension.

Tiny Promise Implementation

Next is the tiny version of Promise. Even though it is quite simple, this code is reasonably faithful to the Scala implementation because Promise does not require concurrency support; that is the job of Future.

Scala code
class Promise[T] {
  val future = new Future[T]
def complete(result: Try[T]): Promise[T] = { if (result.isSuccess) future.value.set(result.get) else future.value.fail(result.failed.get) this }
def failure(cause: Throwable): Promise[T] = { future.value.fail(cause) this }
def success(value: T): Promise[T] = { future.value.set(value) this }
final def tryCompleteWith(other: Future[T]): Promise[T] = { if (!future.isCompleted) { if (future.value.isSuccess) future.value.set(other.value.success) else future.value.fail(other.value.failed) } this }
def tryFailure(cause: Throwable): Boolean = { if (future.isCompleted) { false } else { future.value.fail(cause) true } }
def trySuccess(value: T): Boolean = { if (future.isCompleted) { false } else { future.value.set(value) true } } }
object Promise { def apply[T](): Promise[T] = new Promise[T]
def failed[T](throwable: Throwable): Promise[T] = { val promise = new Promise[T] promise.future.value.fail(throwable) promise }
def successful[T](value: T): Promise[T] = { val promise = new Promise[T] promise.future.value.set(value) promise } }

TinyFutureDemo

Once again, this demo code does NOT show you how you would obtain values from real Scala Futures. I only show you this code so you can see how WriteOnce and Promise relate to Future. The fragments highlighted in yellow are what I want you to notice; the fragments highighted in black are NOT how you should work with futures or promises, they are just there for this quick little demo. Don’t pick up these bad habits.

Scala code
object TinyFutureDemo extends App {
  import TinyFuture._
val promise1 = Promise.successful(1) println(s"Value of promise1 = ${promise1.future.value.success}")
val promise2 = Promise.failed(new Exception("The knurblefritz has been badly gurbled")) println(s"Throwable of promise2 = ${promise2.future.value.failed}")
val future1 = Future.successful(3+5) println(s"value of future1 = ${future1.value.success}")
val future2 = Future.failed(new Exception("The omdedomdom has been hoomified")) println(s"Throwable of future2 = ${future2.value.failed.getMessage}") }

You can run this by typing:

Shell
$ sbt "runMain multi.futures.TinyFutureDemo"
Value of promise1 = 1
Throwable of promise2 = java.lang.Exception: The knurblefritz has been badly gurbled
value of future1 = 8
Throwable of future2 = The omdedomdom has been hoomified 

* indicates a required field.

Please select the following to receive Mike Slinn’s newsletter:

You can unsubscribe at any time by clicking the link in the footer of emails.

Mike Slinn uses Mailchimp as his marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp’s privacy practices.