Skip to content
Peter Nerg edited this page Aug 27, 2021 · 26 revisions

Future/Promise

The Future/Promise concept is the way to separate the execution/job side (Promise) from the client/receiver side (Future).
The following simple example illustrates the concept.
We have a method that returns a Future that some time in the future will hold a string containing the echoed name provided to the method.

public Future<String> echoName(String name) {
    Promise<String> promise = Promise.apply();
    new Thread(() -> {
       // pseudo-code to illustrate some execution time
       pauseRandomTime();
       // the actual response
       promise.success("Hello " + name);
    }).start();
    return promise.future();
}

The following image outlines an example of using a Promise/Future pair as illustrated in the above code snippet.

The examples provided on this Wiki are only a subset of the full functionality.
For a complete set of functionality and examples refer to the Java Doc

Promise

The Promise is the promise to deliver a value at some time in the future.
This is the handle the actual computation side of of the job uses. Once a job is finished it will report the outcome to the Promise which in turn relays it to the Future the client/application is monitoring.
A promise can be fulfilled either by invoking success or failure but not both. Nor can success/failure be invoked twice.
The principle is that a Promise will deliver exactly one successful or failure response.
The successful response is of any type whilst the failure is expected to be of type (or subclass of) Throwable.

Future

The Future is the bearer of a value-to-be, an execution yet to be finished.
The purpose is to have a placeholder for a value from an asynchronous computation.
The Future is designed for a single value response, if you are looking for a mechanism to provide 0..n amount of responses I'd suggest you look at the Observable mechanism provided in the reactive pattern.
One good implementation for this is RxJava.
In contrast to the java.util.concurrent.Future provided in the SDK this Future is designed for non-blocking operation. The idea is to let the user/developer decide whether to block for an answer or to have the answer passed as an event via one of the several subscription possibilities.

The Executor

One can go about to manually create Promise/Future pairs and allocate threads to execute the Promise.
Or one can use the provided specialized Executor.
Let's take this case study where we have a method square that is supposed to deliver a Future containing the result of squaring the provided value. I.e. the signature is:

Future<Integer> square(int value)

Now one could go off and implement it like this:

public static Future<Integer> square(int value) {
    Promise<Integer> p = Promise.apply();
    new Thread(() -> p.success(value * value)).start();
    return p.future();
}

Or one uses the built in executor:

public static Future<Integer> square(int value) {
    return executor.execute(() -> value * value);
}

Or one can let the Future automatically perform its execution.
This is explained further down in the examples.

public static Future<Integer> square(int value) {
    return Future(() -> value * value);
}

The Executor instance is created from the Executors class.
E.g.:

Executor executor = Executors.createCachedThreadPoolExecutor(r -> new Thread(r))

Though it's recommended to always provide a proper ThreadFactory with the purpose of naming the threads for traceability.
A neater way would be:

Executor executor = Executors.createCachedThreadPoolExecutor(new NamedSequenceThreadFactory("SquareExecutor"));

Auto-generation and execution of Future

In addition to manually using the Executor class for performing asynchronous computations one can let the Future class deal with that automatically.
This is done by using Future.apply(...) or statically importing the Future method from the companion class to the Future.

import static javascalautils.concurrent.FutureCompanion.Future;
 
Future<Integer> resultSuccess = Future(() -> 9 / 3); // The Future will at some point contain: Success(3)
Future<Integer> resultFailure = Future(() -> 9 / 0); // The Future will at some point contain: Failure(ArithmeticException)

or re-writing one of the examples from Try to become asynchronous.
Note that the threading/execution is managed automatically as well as any exception handling.
A successful execution will result in a Future containing the first line of the file, whilst any exception will render in a failed Future containing the raised exception.

public static Future<String> readFirstLineFromFile(String file) {
   return Future(() -> {
       //the surrounding try-with-resources is only to get auto-close on the reader
       try (LineNumberReader r = new LineNumberReader(new FileReader(file))) {
           return r.readLine();
       }
   });
}

The flow for executing the above code snippet can be illustrated by the following chart.

Examples

Asynchronous/event based responses

As mentioned the difference to the Future provided in the Java SDK this implementation is by nature non-blocking.
The way to receive the responses is via a series of event handlers.
One can choose which types of responses to receive notification for: successful, failure or a combination of both. Let's for the sake of examples assume this Future:

Future<String> future = ...

To receive a successful response one installs a success listener:

future.onSuccess(s -> System.out.println(s));

To receive a failure response one installs a failure listener:

future.onFailure(ex -> System.out.println(ex.getMessage()));

Of course both listeners can be installed on the same Future. In fact one can install multiple listeners of the same type to the Future. Thus multiple clients can receive notifications from the same Future instance.

Should one want to get notified of either outcome successful/failure then there is the possibility to install a handler that listens to both outcomes.
Using the onComplete you'll get a Try handed to you letting you decide whether it's a success/failure.

future.onComplete(t -> System.out.println(t.isSuccess()));

Side effecting functions without return type

For situations where you want to apply a function that has a side effect but now return type there's a special method on the FutureCompanion class.
The purpose is to allow the user to invoke a side-effecting function that may succeed/fail but has now return type.
E.g. deleting something from a database may not have an interesting return type.
One is only interested of the outcome, Success/Failure.
The following example illustrates some pseudo-code that performs a deletion in some database.
The delete operation may potentially fail with an exception but we're not interested in any return data.
The construct will return result in Unit in case of successful, else a Failure with the exception.

import static javascalautils.concurrent.FutureCompanion.Future;

Future<Unit> f = Future(() -> {
  database.delete(someId);
});

Blocking responses

For convenience the Future provides a blocking method allowing the user to block for the response.
Whilst this is against the reactive principles it might sometimes be necessary if the rest of your application anyways is synchronous by nature.
The method result allows you to block for a result for a provided duration.
Any successful result is produced as a return whilst failure responses are thrown as exceptions.

Future<String> future = ...
String result = future.result(5, TimeUnit.SECONDS);

or using a Duration

String result = future.result(Duration.ofSeconds(5));

Another approach is to use the ready method. Just like result it will wait for a provided duration. But the difference is that the method will not return the value of the result nor throw the exception of the failure.
The method only blocks and waits for the Future to complete. If completed in time it will return this else a TimeoutException is raised.
E.g. using the Future declare above.

Future<String> result = future.ready(Duration.ofSeconds(5));

Mapping Futures

One intriguing concept is to map a Future to some other Future.
Now this may seem completely out of place but let's take this simple example where we have a Future of the type String which we intend to make to a Future of numerical type.

Future<String> future = ...
//our mapping function simply takes the length of the string
Future<Integer> mapped = future.map(s -> s.length());
mapped.onSuccess(length -> System.out.println("The string was this long:"+length));

There's still no data in the Future until it has been completed but when the original Future is completed with a String our mapped one is immediately completed with an Integer representing the length of the string.

Flattening the Future

Let's consider a situation where we have two Future instances expected to contain an integer.
Now we want to sum the values of the integers-to-be. One can of course go about and wait for the futures to finish, either via registering a onSuccess handler or blocking and waiting for a response.
A neater way is to map the two futures into a new future containing the sum of the two values-to-be.
Let's use the square method from an earlier example on this page to create two futures.

Future<Integer> f1 = square(2);
Future<Integer> f2 = square(3);

Now how to sum these?
Simply applying map won't do:

Future<Future<Object>> map = f1.map(v1 -> f2.map(v2 -> v1 + v2))

We tried to map future1 to get it's value and then use that value to map a sum with the value from future2.
It did get us somewhere but left us with a Future containing another Future.
What we want is to flat map the future.

Future<Object> mapped = f1.flatMap(v1 -> f2.map(v2 -> v1 + v2));
System.out.println(mapped.result(5, TimeUnit.SECONDS));

Now we have a piece of code that will take future1, flat map it with future2 by applying the map method on future2 providing the value1 to sum with value2.
Tricky?
It deserves some meditation to get around but can prove to be a powerful tool once you get the hang of it.
The result, well it will of course print 13 (2x2 + 3x3).
Don't believe me? Try out yourself.

Stream of Futures to Stream of values

In the scenario where you have a list or stream of Futures, they can be turned into a single Future with a Stream with all the results from the Futures.
This allows for easy management of multiple Futures.
Example:

List<Future<String> list = ....
Future<Stream<String>> future = Future.sequence(list.stream());

Parallel mapping data

The traverse operation takes a Stream of data and applies a function to all data in parallel.
The level of parallelism is determined by the amount of free threads in the executor pool. The example below takes a Stream of strings and applies a function that counts the length of each string.
The trick is that the function in applied in parallel and yields a Future containing a Stream with the mapped data.

Stream<String> stream = Stream.of("Peter", "was", "here");
Future<Stream<Integer>> future = Future.traverse(stream, v -> Future(() -> v.length()));