Mike Slinn

Future Callbacks

— Draft —

Published 2014-09-22. Last modified 2019-06-27.
Time to read: 6 minutes.

This lecture discusses how callbacks work for Scala Futures, including completing Futures successfully and with failure. The transcript has been updated for Scala 2.12 – happily, the advice given previously was prescient so the recommendations given in this lecture have not changed.

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

We discussed ExecutionContexts in the MultiThreading lecture. Futures require an ExecutionContext, and they are backed by thread pools. Scala provides a default implementation, available by importing concurrent.ExecutionContext.Implicits.global.

We need five Promise[String] instances for this lecture's code examples, backed by the default ExecutionContext. We'll create the first Promise with a value, and leave the other four pending.

Scala REPL
scala> import concurrent._
import concurrent._
scala>
import concurrent.ExecutionContext.Implicits.global import concurrent.ExecutionContext.Implicits.global
scala>
import scala.util.{Try, Success, Failure} import scala.util.{Try, Success, Failure}
scala>
val promises: Vector[Promise[String]] = Promise.successful("Hi there") +: (1 to 4).map { _ => Promise[String]() }.toVector promises: Vector[scala.concurrent.Promise[String]] = Vector(scala.concurrent.impl.Promise$KeptPromise@1d3ac898, scala.concurrent.impl.Promise$DefaultPromise@1b73be9f, scala.concurrent.impl.Promise$DefaultPromise@628c4ac0, scala.concurrent.impl.Promise$DefaultPromise@7b84fcf8, scala.concurrent.impl.Promise$DefaultPromise@30b19518)

Here are two utility methods that we will use to report on the status of the promises we just created:

Scala code
def promiseStatus(i: Int): Unit =
  if (promises(i).isCompleted) {
    promises(i).future.value.get match {
      case Success(value) => println(s"promises($i).future.value='$value'.")
      case Failure(exception) => println(s"promises($i).future exception message: '${exception.getMessage}'.")
    }
  } else println(s"promises($i) is pending.")
def promiseStatuses(msg: String): Unit = { println(s"\n== $msg ==") 0 until promises.length foreach { promiseStatus } println() }

The promiseStatuses method uses the shorthand discussed in the Higher-Order Functions lecture.

Now let's see what the state of the promises are:

Scala REPL
scala> promiseStatuses("Five Promises Started")
scala> == Five Promises Started ==
promises(0).future.value='Hi there'.
promises(1) is pending.
promises(2) is pending.
promises(3) is pending.
promises(4) is pending. 
Callbacks are only useful for their side effects

Callback Methods

We can get the value of the promise after the value is written via the Promise's future property. For a Promise[T], the type of the future property is Future[T]. The Future class provides four two callback methods.

onSuccess

onSuccess[U](pf: PartialFunction[T, U])(implicit executor: ExecutionContext): Unit

Deprecated in Scala v2.12. onSuccess is called if the Future completed successfully. Returns Unit so it cannot be composed.

Removed in Scala v2.13

onFailure

onFailure[U](pf: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Unit

Deprecated in Scala v2.12. onFailure is called if the Future completed with a Throwable value. Returns Unit so it cannot be composed.

Removed in Scala v2.13

onComplete

onComplete[U](f: (Try[T]) => U)(implicit executor: ExecutionContext): Unit

onComplete is called regardless of whether the Future completed successfully or not. Returns Unit so it cannot be composed. I recommend this callback not be used; use andThen instead.

andThen

andThen[U](pf: PartialFunction[Try[T], U])(implicit executor: ExecutionContext): Future[T]
andThen chains a sequence of calls together, which are executed in the order listed. andThen works just the same as onComplete, but multiple andThen clauses can be composed, and they are executed in the order listed. I recommend this callback be used instead of onComplete. andThen is a composable callback; however, andThen is not a combinator because it does not transform the Future.

You can define multiple callbacks for a Future including one, some or all of the above methods as desired. None of these callbacks are able to alter the Future they are associated with. The Future Combinators lecture explores how you can use combinators to create modified versions of a Future.

These callbacks are either passed a Try[Future[T]] or the value of a Try. onComplete and andThen receive a Function1 or PartialFunction1, respectively, which have a Try as input. The Try passed to either of the callbacks might be an instance of Success or Failure.

Callbacks Trigger Immediately for Promises Created With Values

We can use the onComplete callback to do something with the value of a Future once it is set. Since promises(0)'s value was established when it was created, the onComplete and andThen callbacks fire as soon as the the expression is evaluated.

Scala REPL
scala> promises(0).future onComplete {
     |   case Success(value) =>
     |     println(s"promises(0) completed successfully with value='$value'.")
     |
     |   case Failure(throwable) =>
     |     println(s"promises(0) onComplete exception; message: '${throwable.getMessage}'.")
     | }
promises(0) completed successfully with value='Hi there'. 

Do you recognize this expression as being a partial function? If not, you should review the Partial Functions lecture earlier in this course.

Callbacks Trigger After Promises Complete

A Future's callbacks will be executed if it is created with a value or when its result becomes available. Let's set up two callbacks for promises(1): andThen and onComplete. Both of these callbacks will trigger when promises(1) completes.

Scala REPL
scala> promises(1).future onComplete {
     |   case Success(value) =>
     |     println(s"promises(1) onComplete success: value='$value'.")
     |
     |   case Failure(throwable) =>
     |     println(s"promises(1) onComplete exception; message: '${throwable.getMessage}'.")
     | }
scala> promises(1).future andThen { | case Success(value) => | println(s"promises(1) andThen success: value='$value'.") | | case Failure(throwable) => | println(s"promises(1) andThen exception; message: '${throwable.getMessage}'.") | }

Because I am using the Scala REPL and I type relatively slowly compared to a CPU I can check to see if promises(1) has completed, however you would not normally write this in an actual program because the promise often would not be completed by the time the test is made.

Scala REPL
scala> promises(1).isCompleted
res0: Boolean = false 

Now we will trigger the defined promises(1) callbacks by completing promises(1). Notice that the andThen and onComplete callbacks both triggered, but not in the declared order, and the onComplete callback's output appeared after the REPL prompt.

Scala REPL
scala> promises(1).success("The tao that is knowable is not the true tao")
promises(1) andThen success: value='The tao that is knowable is not the true tao'.
promises(1) onComplete success: value='The tao that is knowable is not the true tao'.
res1: scala.concurrent.Promise[String] = scala.concurrent.impl.Promise$DefaultPromise@1b73be9f
promises(1) onComplete success; value='The tao that is knowable is not the true tao'. 

Here is another way to complete a Promise, by passing the Success subclass of Try to Promise.complete. As you can see, promises can only be completed once because they are write-once objects.

Scala REPL
scala> promises(1).complete(Success("The tao that is knowable is not the true tao"))
java.lang.IllegalStateException: Promise already completed.
  at scala.concurrent.Promise$class.complete(Promise.scala:55)
  at scala.concurrent.impl.Promise$DefaultPromise.complete(Promise.scala:153)
  ... 

Let's see what promiseStatuses reports now:

Scala REPL
scala> promiseStatuses("After Promises(1) Completed")
== After Promises(1) Completed ==
promises(0).future.value='Hi there'.
promises(1).future.value='The tao that is knowable is not the true tao'.
promises(2) is pending.
promises(3) is pending.
promises(4) is pending. 

Partial Functions Can Have Many Cases

I showed onComplete and andThen with partial functions that have one or two cases, but you could write more specific partial functions. Here is an example of an onComplete callback with two cases:

Scala code
val optionIntPromise1 = Promise[Option[Int]]()
optionIntPromise1.future OnComplete {
  case Success(Some(value)) => println(s"The value $value only is matched if the future returns an Option")
  case Success(None) => println(s"None could only be matched if the future returns an Option")
}
optionIntPromise1.success(Some(3))
Await.ready(optionIntPromise1.future, 30 minutes)

The Await.ready call causes the thread that is executing this code to cease doing work until the future passed to it completes. The second argument causes a timeout to occur after 30 minutes passes, and if the thread is still blocked then Await.ready returns Unit, and the thread resumes execution.

Here is an example of an onComplete callback with 3 cases:

Scala code
val optionFilePromise1 = Promise[Option[java.io.File]]()
optionFilePromise1.future onComplete {
  case Failure(fnfe: java.io.FileNotFoundException) => println(s"Hey, you are missing a file! ${fnfe.getMessage}")
  case Failure(ioe: java.io.IOException) => println(s"Could be burgler? ${ioe.getMessage}")
  case Failure(throwable) => println(s"optionFilePromise1 throwable message: '${throwable.getMessage}'.")
}
optionFilePromise1.failure(new java.io.IOException("Oh noes!"))

Here is an example of an andThen callback with 5 cases:

Scala code
val optionFilePromise2 = Promise[Option[java.io.File]]()
optionFilePromise2.future andThen {
  case Success(Some(value)) => println(s"The value $value only is matched if the future returns an Option")
  case Success(None) => println(s"None could only be matched if the future returns an Option")
  case Failure(fnfe: java.io.FileNotFoundException) => println(s"Hey, you are missing a file! ${fnfe.getMessage}")
  case Failure(ioe: java.io.IOException) => println(s"Could be burgler? ${ioe.getMessage}")
  case Failure(throwable) => println(s"optionFilePromise2 exception message: '${throwable.getMessage}'.")
}
optionFilePromise2.failure(new java.io.IOException("Oh noes!"))

The number of cases in the partial function is only limited by the types and values returned by the Future.

Chaining andThen Callbacks

onComplete returns Unit, so it cannot be chained with other methods, unfortunately. Happily, andThen returns the original Future, so we can chain multiple andThen clauses together. Notice that the partial function passed to the first andThen contains cases for Success and Failure, while the partial function passed to the second andThen has one catch-all case.

Scala REPL
scala> promises(2).future andThen {
     |   case Success(value) => println(s"promises(2) andThen success: promise3 value='$value'.")
     |   case Failure(throwable) => println(s"promises(2) andThen exception message: '${throwable.getMessage}'.")
     | } andThen {
     |   case _ => println("promises(2).andThen.andThen - Will that be all, master?")
     | }
scala> promises(2).complete(Success("Great achievement looks incomplete, yet it works perfectly")) promises(2) andThen success value='Great achievement looks incomplete, yet it works perfectly'. promises(2).andThen.andThen - Will that be all, master?

You can think of andThen as being like onComplete, but andThen returns the original Future, instead of returning Unit like onComplete does. I never use onComplete, I always use andThen instead. We'll see an important reason at the end of this lecture.

Let's see what promiseStatuses reports now:

Scala REPL
scala> promiseStatuses("After Promises(2) Completed")
== After Promises(2) Completed ==
promises(0).future.value='Hi there'.
promises(1).future.value='The tao that is knowable is not the true tao'.
promises(2).future.value='Great achievement looks incomplete, yet it works perfectly'.
promises(3) is pending.
promises(4) is pending. 

Completing With Failure

As we discussed in the Traits / Mixins lecture in the Introduction to Scala course, NoStackTrace can increase efficiency if it is used to define an object or a class, but you lose stack traces which might be useful for debugging errors later on, leading to questions like "what created this Throwable?" If you are using Futures and Promises and therefore your program switches execution contexts at lot, the cost of populating the stack trace might not be noticeable. John Rose of Oracle wrote an interesting article that discusses this topic.

Scala REPL
scala> import scala.util.control.NoStackTrace
import scala.util.control.NoStackTrace
scala>
class ExceptTrace(msg: String) extends Exception(msg) with NoStackTrace defined class ExceptTrace
scala>
promises(3).future.onComplete { | case Failure(exception) => | println(s"promises(3) completed with exception message: '${exception.getMessage}'.") | }
scala> promises(3).failure(new ExceptTrace("Boom!")) promises(3) completed with exception message: 'Kaboom'. res3: scala.concurrent.Promise[String] = scala.concurrent.impl.Promise$DefaultPromise@7b84fcf8

The above is efficient; only one class is defined, an instance will be created each time one is required.

Let's examine the value of the completed Future that is associated with each Promise.

Scala REPL
scala> promiseStatuses("After Promises(3) Completed")
== After Promises(3) Completed ==
promises(0).future.value='Hi there'.
promises(1).future.value='The tao that is knowable is not the true tao'.
promises(2).future.value='Great achievement looks incomplete, yet it works perfectly'.
promises(3).future exception message: 'Kaboom'.
promises(4) is pending. 

If some of your exceptions are invariant in all their properties, you could define a singleton object instead of a class instance for even greater efficiency.

Scala REPL
scala> object TheExceptTrace extends Exception("Boom!") with NoStackTrace
defined module TheExceptTrace
scala>
promise5.complete(Failure(TheExceptTrace)) promise4: scala.concurrent.Promise[Nothing] = scala.concurrent.impl.Promise$KeptPromise@5f2adaa8

Let's examine the value of the completed Future that is associated with each Promise.

Scala REPL
scala> promiseStatuses("Five Promises Concluded")
== Five Promises Concluded ==
promises(0).future.value='Hi there'.
promise(1).future.value='The tao that is knowable is not the true tao'.
promise(2).future.value='Great achievement looks incomplete, yet it works perfectly'.
promise(3).future exception message: 'Kaboom'.
promise(4).future exception message: 'Kablam'. 

Creating a Future Directly

You don't have to create a Promise in order to create a Future – you can create a Future directly, using its companion object's apply method. Let's define a tail-recursive factorial method that will take a long time to run because it is CPU-bound, and execute it within a Future.

Scala REPL
scala> def factorial(number: BigInt): BigInt = {
     |  import annotation.tailrec
     |  @tailrec def fact(total: BigInt, number: BigInt): BigInt = {
     |  if (number == BigInt(1))
     |    total
     |  else
     |    fact(total* number, number - 1)
     |  }
     |  fact(1, number)
     | }
factorial: (number: BigInt)BigInt
scala>
Future(factorial(12345)) res4: scala.concurrent.Future[BigInt] = scala.concurrent.impl.Promise$DefaultPromise@115989a9

You will find factorial defined in the multi package object, in courseNotes/src/main/scala/multi/package.scala.

The factorial method is automatically lifted to a Function1[BigInt, BigInt] when passed to Future.apply.

Now let's run the computation within a Future and provide an onComplete callback.

Scala REPL
scala> val f1 = Future(multi.factorial(12345))
scala> f1 onComplete { | case Success(value) => println(s"factorial success #1: factorial(12345)=$value") | case Failure(exception) => println(s"factorial onComplete completed with exception message: '${exception.getMessage}'.") | } After a delay (depending on how fast you type), huge output!

Finally, let's set up a same computation using the andThen callback instead of onComplete.

Scala REPL
scala> val f2 = Future(multi.factorial(12345)) andThen {
     |  case Success(value) => println(s"factorial success #2: factorial(12345)=$value")
     |  case Failure(exception) => println(s"factorial andThen completed with exception message: '${exception.getMessage}'.")
     | }
After a delay, huge output! 

Notice that the REPL prompt reappeared, then a bit later the output of the andThen factorial appeared. Also notice that the assignment to f2 was performed in the same expression that set up the andThen callback. This was not possible with onComplete, because onComplete returns Unit

Exercise – Test Your Understanding

The preceding onComplete always generates output when used from the REPL, but when the demo program runs the onComplete callback sometimes does not generate output. Take a look at the demo program (multi/futures/FutureCallback.scala) and try to understand why. The program ends with these two statements.

Scala code
Future.sequence(List(f1, f2) ::: promises.map(_.future).toList) andThen { case _ => System.exit(0) }
synchronized { wait() }

We know that the last statement pauses the main thread. Let's examine the penultimate statement for a moment. First, look at this expression:

Scala code
List(f1, f2) ::: promises.map(_.future).toList

This expression creates a List of the Futures from each promise in promises and concatenates the List[Future[_]] with a List of the Futures f1 and f2, yielding a List[Future[_]] containing every Future created in the entire demo program, including the Futures contained in the Promises. We'll learn how Future.sequence works in the Working With Collections of Futures lecture, but for now suffice it to say that it forms a new Future from the list of Futures. When the Future of the List[Future[_]] completes, the andThen callback triggers.

Why would the onComplete callback sometimes not generate output?

Solution and Takeway

The andThen callback returns a new Future with the same value as the Future it was invoked from. The result of andThen was assigned to f2, and f2's completion was used as one of the criteria for ending the program. Conversely, because onComplete does not return a value, we were forced to use the completion of the original future (f1) as one of the conditions for ending the program. There is no guarantee that the callback will be called or completed before its thread is terminated. This means that sometimes f1's callback is able to complete, and sometimes the program ends before output is generated.

andThen returns a Future after the callback has completed. The other callback (onComplete) cannot be used in a convenient manner without blocking such that an assertion can be made that these callbacks have completed at a later time.


* 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.