Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get combined effects of whenComplete and thenCompose?

Tags:

java

java-8

I am trying to come up with a CompletableFuture with the combined effects of whenComplete and thenCompose, specifically:

  1. Returns a CompletionStage instead of just a result, similar to thenCompose.
  2. Executes even when previous stage completes exceptionally, similar to whenComplete, and does not stop the exception from propagating.

This post is close to what I'm trying to achieve but I don't want to use handle which hides the exception. Thanks for any ideas.

like image 222
Zigzagoon Avatar asked Jun 19 '19 23:06

Zigzagoon


People also ask

What does CompletableFuture runAsync do?

runAsync. Returns a new CompletableFuture that is asynchronously completed by a task running in the given executor after it runs the given action.

What is CompletionStage?

A stage of a possibly asynchronous computation, that performs an action or computes a value when another CompletionStage completes. A stage completes upon termination of its computation, but this may in turn trigger other dependent stages.

When CompletableFuture is done?

The takeaway of this example is two things: A CompletableFuture is executed asynchronously when the method typically ends with the keyword Async. By default (when no Executor is specified), asynchronous execution uses the common ForkJoinPool implementation, which uses daemon threads to execute the Runnable task.

What is CompletableFuture completedFuture?

completedFuture() is a static method of the CompletableFuture class and is used to get a new CompletableFuture that is in the completed stage, with the passed value as the result of the future. The completedFuture method is defined in the CompletableFuture class.


1 Answers

I don't believe CompletionStage or CompletableFuture provides any single method for this. However, combining handle with thenCompose should do what you want, if I understand your requirements correctly.

A handle stage is executed whether the parent stage has completed normally or exceptionally and gives you access to the result or error, respectively. From this stage you could return another CompletionStage which would either be completed normally or exceptionally depending on what arguments the handle stage receives.

handle((T result, Throwable error) -> {
    if (error != null) {
        return CompletableFuture.<T>failedStage(error);
    } else {
        return processResult(result); // returns CompletionStage<T>
    }
});

Now you have a CompletionStage<CompletionStage<T>>. Now we execute a flat map operation by invoking thenCompose:

thenCompose(Function.identity());

Which gives us a CompletionStage<T>. This CompletionStage<T> will be whatever instance was returned by handle. If that instance was a failed stage then the exception is still propagated; otherwise, the result is passed to whatever stage is dependent on the thenCompose stage and processing continues normally.

You can see this with the following example:

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

public class Main {

    public static void main(String[] args) {
        methodThatReturnsCompletionStage()
                .handle((result, error) -> {
                    if (error != null) {
                        return CompletableFuture.<String>failedStage(error);
                    } else {
                        return processResult(result);
                    }
                })
                .thenCompose(future -> {
                    System.out.println("#thenCompose invoked");
                    return future; // Identity function
                })
                .thenApply(result -> {
                    System.out.println("#thenApply invoked");
                    return result; // Identity function (exists to show intermediary stage)
                })
                .whenComplete((result, error) -> {
                    System.out.println("#whenComplete invoked");
                    if (error != null) {
                        error.printStackTrace(System.out);
                    } else {
                        System.out.println(result);
                    };
                });
    }

    private static CompletionStage<String> methodThatReturnsCompletionStage() {
        return CompletableFuture.completedStage("Hello");
        // return CompletableFuture.failedStage(new RuntimeException("OOPS"));
    }

    private static CompletionStage<String> processResult(String result) {
        return CompletableFuture.completedFuture(result + ", World!");
    }

}

This will result in each stage being invoked and an output of Hello, World!. But if you switch methodThatReturnsCompletionStage() to return the failed stage instead then thenApply is skipped (because the future has failed) and the exception is given to whenComplete (which, like handle, is invoked for both normal or exceptional completion).

Note: Everything above uses the CompletionStage interface directly but using CompletableFuture works just as well (and may be preferable).

like image 134
Slaw Avatar answered Oct 29 '22 04:10

Slaw