Published 2014-09-22.
Last modified 2019-06-27.
Time to read: 6 minutes.
This lecture discusses how callbacks work for Scala Future
s, including completing Future
s 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/
.
We discussed ExecutionContext
s in the MultiThreading lecture.
Future
s 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> 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:
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> 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.
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[U](pf: PartialFunction[T, U])(implicit executor: ExecutionContext): UnitonSuccess -
Deprecated in Scala v2.12.
onSuccess
is called if theFuture
completed successfully. ReturnsUnit
so it cannot be composed.Removed in Scala v2.13
-
onFailure[U](pf: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): UnitonFailure -
Deprecated in Scala v2.12.
onFailure
is called if theFuture
completed with aThrowable
value. ReturnsUnit
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 theFuture
completed successfully or not. ReturnsUnit
so it cannot be composed. I recommend this callback not be used; useandThen
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 asonComplete
, but multipleandThen
clauses can be composed, and they are executed in the order listed. I recommend this callback be used instead ofonComplete
.andThen
is a composable callback; however,andThen
is not a combinator because it does not transform theFuture
.
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> 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> 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> 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> 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> 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> 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:
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:
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:
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> 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> 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 Future
s and Promise
s 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> 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> 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> 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> 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> 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/
.
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> 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> 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.
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:
List(f1, f2) ::: promises.map(_.future).toList
This expression creates a List
of the Future
s from each promise
in
promises
and concatenates the List[Future[_]]
with a List
of the
Future
s f1
and f2
,
yielding a List[Future[_]]
containing every Future
created in the entire demo program,
including the Future
s contained in the Promise
s.
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 Future
s.
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.
© Copyright 1994-2024 Michael Slinn. All rights reserved.
If you would like to request to use this copyright-protected work in any manner,
please send an email.
This website was made using Jekyll and Mike Slinn’s Jekyll Plugins.