Step away from the Future

JDK5 Futures

As featured in ExecutorService:

ExecutorService threadPool = Executors.newThreadPool();
Future<Result> future = threadPool.submit(() => calculate(arg));
Result result = future.get(); // throws InterruptedException, ExecutionException

// or..
Result result = future.get(1, TimeUnit.MINUTES); // or TimeoutException

What's wrong with Future?

A Future is only good for one thing: blocking on it until it completes.

  • You need to dedicate a thread to doing that
  • You can spin round polling them with no/short timeout, but yuk

Dealing with multiple Futures for anything other than "wait for all of them to complete" gets harder and harder.

How do you "wait for the first one to be complete"? (apart from specialised constructs like CompletionService)

Elsewhere in Java: Ning, Guava

Both Ning and Guava define a ListenableFuture which calls a callback when the task is complete.

Guava has a Futures utility class for combining them together, e.g. transform, whenAllSucceed

Elsewhere on the JVM: scala.Future

scala.Future is quite different to JDK5 Future, and provides methods for transforming/composing (map, flatMap etc)

Elsewhere: JavaScript

JavaScript (outside node.js itself) has largely moved to using Promises to encapsulate asyncronous completion:

new Promise((resolve, reject) => ajax({
		success: data => resolve(data),
		error: (xhr, status, e) => reject(e) }))
	.then(data => setState({ data, error: null }))
	.catch(error => setState({ error, data: null }));

Common features

All these other libraries provide a way of building a pipeline (actually, a graph) of (possibly) asynchronous actions, which can bear either a positive result value or an exception value.

With JDK8, time for Java to join the party.

CompletableFuture<T>

CompletableFuture actually implements Future, so could be used the same way. Nevertheless:

CompletableFuture<Result> future =
    CompletableFuture.supplyAsync(() => calculate(arg), executor);
Result result = future.join();

// JDK9 only
Result result = future.orTimeout(1, TimeUnit.MINUTES).join();

They also work like promises/Scala futures, implementing the CompletionStage with a plethora of transforming methods:


supplyAsync(this::calculate) // or runAsync(Runnable)
  .thenApply(this::mangleResult) // like scala.Future#map, or Promise.then
  .thenApplyAsync(this::mangleResult, executor) // transform run on a different thread
  .thenCompose(this::nextStage) // like scala.Future#flatMap, or Promise.then
  .thenCombine(otherFuture, (v1, v2) -> x)) // join two futures ("and")
  .applyToEither(otherFuture, (v1, v2) -> x)) // join two futures ("or")
  .exceptionally(exception -> x) // like Promise.catch
  .handle((value, exception) -> x) // better
					

Practically all have three variants: synchronous, asynchronous using default fork-join pool, asynchronous using specified Executor.

Say goodbye to ExecutorService too - these only need the Executor SAM interface

Also a smattering of utility methods:

CompletableFuture<?> allOf = allOf(future1, future2, ...); // or anyOf
CompletableFuture<Result> completed = completedFuture(value);
CompletableFuture<?> failed = failedFuture(value); // JDK9
					

They also work like Guava SettableFuture or Scala Promise objects:

CompletableFuture<Result> future = new CompletableFuture<>();
future.complete(value);
future.completeExceptionally(new Exception());
future.completeAsync(() => this::calculate); // JDK9

Examples

  • https://gist.github.com/araqnid/68dd963745ddfb60b6f4d3c0497823d7