Home > Articles

This chapter is from the book

26.2 Callbacks

A callback is a piece of code that is registered for execution, often later (asynchronous callback). Modern implementations of futures—including Scala’s Future and Java’s CompletableFuture—offer a callback-registration mechanism. On a Scala future, you register a callback using method onComplete, which takes as its argument an action to apply to the result of the future. Because a future can end up with an exception instead of a value, the input of a callback action is of type Try (see Section 13.3).

On a future whose task is still ongoing, a call to onComplete returns immediately. The action will run when the future finishes, typically on a thread pool specified as execution context:

Scala

println("START")
given ExecutionContext = ... // a thread pool

val future1: Future[Int]    = ... // a future that succeeds with 42 after 1 second
val future2: Future[String] = ... // a future that fails with NPE after 2 seconds
future1.onComplete(println)
future2.onComplete(println)

println("END")

This example starts a 1-second task and a 2-second task and registers a simple callback on each. It produces an output of the following form:

main at XX:XX:33.413: START
main at XX:XX:33.465: END
pool-1-thread-3 at XX:XX:34.466: Success(42)
pool-1-thread-3 at XX:XX:35.465: Failure(java.lang.NullPointerException)

You can see that the main thread terminates immediately—callback registration takes almost no time. One second later, the first callback runs and prints a Success value. One second after that, the second callback runs and prints a Failure value. In this output, both callbacks ran on the same thread, but there is no guarantee that this will always be the case.

You can use a callback in the ad-fetching scenario. Instead of waiting for a customized ad to assemble a page, as in Listing 26.2, you specify as an action what is to be done with the ad once it becomes available:

Scala

Listing 26.3: Ad-fetching example with a callback on a future.
val futureAd: Future[Ad] = Future(fetchAd(request))
val data: Data           = dbLookup(request)
futureAd.onComplete { ad =>
   val page = makePage(data, ad.get)
   connection.write(page)
}

After the connection-handling thread completes the database lookup, it registers a callback action on the ad-fetching task, instead of waiting for the task to finish. The callback action extracts a customized ad from the Try value (assuming no error), assembles the data and ad into a page, and sends the page back as a reply as before. The key difference from Listing 26.2 is that no blocking is involved.

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.