pks
pks

Reputation: 129

reference to a field inside a CompletableFuture which can again be a CompletableFuture : java

I am making call to a service, which is returning CompletableFuture.

The output structure is as follows.

Class Output {
    public String name;
    public Integer age;
}

And I want to make the call to service, and want to go ahead with my execution till the name is present.

Something like,

CompletableFuture<Output> futureOutput = makeServiceCall(input);
String name = futureOutput.get().name;
processName(name); // which will do some costly operations and make use of the name at the end. 

In the above approach, I need to wait till my futureOutput is ready, even though I need it only later.

I look for something like a below approach.

CompletableFuture<Output> futureOutput = makeServiceCall(input);
CompletableFuture<String> futureName = futureOutput.get().name; // This is wrong, but can we create a reference in a similar way?
processName(futureName); // which will do some costly operations and make use of the name at the end. 

Its fine for me to change the signature of processName from String to CompletableFuture<String> but not to CompletableFuture<Output> as Output does not make any sense for that method.

What are the suggested ways to have a future reference which is a field of another future.

Upvotes: 4

Views: 2387

Answers (2)

Boris the Spider
Boris the Spider

Reputation: 61148

Just use CompletableFuture.thenApplyAsync

From the JavaDoc:

Returns a new CompletionStage that, when this stage completes normally, is executed using this stage's default asynchronous execution facility, with this stage's result as the argument to the supplied function.

In your example (where T is the return type of the processName method):

CompletableFuture<Output> future = makeServiceCall(input);
CompletableFuture<T> result = future.thenApplyAsync(o -> processName(o.name));

Now, when the makeServiceCall CompletableFuture completes then another CompletableFuture is generated to wrap an async call to processName - this creates an asynchronous pipeline.

Depending on what you want to do, you might what to use CompletableFuture.thenAcceptAsync instead, for example if processName doesn't return a useful result:

CompletableFuture<Output> future = makeServiceCall(input);
CompletableFuture<Void> result = future.thenAcceptAsync(o -> processName(o.name));

You would probably also want error handling in case this processing doesn't complete, this can be done with CompletableFuture.exceptionally. This adds a callback this is invoked if the processing pipeline terminates with an Exception.

By means for a full example, you could do:

makeServiceCall(input)
    .thenApplyAsync(Output::getName)
    .thenAcceptAsync(this::processName)
    .exceptionally(e -> {
        //log the error
        System.out.printf("Oops - %s%n", e);
        //return some useful default value
        return ProcessingResult.SUCCESSFUL;
    });

This pipeline (while slightly contrived - there is no need to get the name async) is fully asynchronous. There is no need to block the Thread that creates the task any any point; the task will either complete successfully or the failure handler will be invoked.

Upvotes: 7

assylias
assylias

Reputation: 328608

You can supply the result to a new completion stage, for example:

CompletableFuture<Output> futureOutput = makeServiceCall(input);
futureOutput.thenAcceptAsync(output -> processName(output.name));

(or using thenAccept if you want to block until the operation completes).

Upvotes: 0

Related Questions