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.