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’sFutureand Java’sCompletableFuture—offer a callback-registration mechanism. On a Scala future, you register a callback using methodonComplete, 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 typeTry(see Section 13.3).
On a future whose task is still ongoing, a call toonCompletereturns immediately. The action will run when the future finishes, typically on a thread pool specified as execution context:
Scala
println("START")givenExecutionContext = ...// a thread poolvalfuture1: Future[Int] = ...// a future that succeeds with 42 after 1 secondvalfuture2: Future[String] = ...// a future that fails with NPE after 2 secondsfuture1.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 aSuccessvalue. One second after that, the second callback runs and prints aFailurevalue. 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.
valfutureAd: Future[Ad] = Future(fetchAd(request))valdata: Data = dbLookup(request) futureAd.onComplete { ad =>valpage = 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 theTryvalue (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.