简体   繁体   中英

Is there any difference between returning a void vs returning a CompletionStage<Void>?

More specifically, what are the differences, is any, in these two pieces of code, one returning void and other returning CompletionStage<Void> ?

  • What advantages of using one of void or CompletionStage<Void> over the other?
  • In CODE SAMPLE 2, I'm not making use of the variable x in thenCompose . Is there a better way of writing it?
  • Is it a better practice if I return a boolean in the function doValidation and check if true in performTask function?

CODE SAMPLE 1:

public CompletionStage<Response> performTask(Request request) throws MyException {
    doValidation(request);
    return service.performTask(request).thenCompose(serviceResponse -> CompletableFuture.completedFuture(buildMyResponse(serviceResponse)));
}

private void doValidation(Request request) throws MyException {
    CompletableFuture.runAsync(() -> {
        // Validate
        if (*someCondition*) {
            throw new MyException(.....);
        }
    });
}

CODE SAMPLE 2:

public CompletionStage<Response> performTask(Request request) throws MyException {
    return doValidation(request).thenCompose(x -> {
        return service.performTask(request).thenCompose(serviceResponse -> CompletableFuture.completedFuture(buildMyResponse(serviceResponse)));
    });
}

private CompletionStage<Void> doValidation(Request request) throws MyException {
    return CompletableFuture.runAsync(() -> {
        // Validate
        if (*someCondition*) {
            throw new MyException(.....);
        }
    });
}

I am addressing your first question

What advantages of using one of void or CompletionStage<Void> over the other?

To understand the difference, you should try logging the flow. It will help you identify how different they both work.

Here is an example similar to the approaches you followed. I came up with my own example because I don't know your code.

public class Test {

    public static void main(String[] args) {
        tryWithVoid();
        //      tryWithCompletionStageVoid();
    }

    public static void tryWithVoid() {
        System.out.println("Started");
        validate(null);
        System.out.println("Exit");
    }

    public static void tryWithCompletionStageVoid() {
        System.out.println("Started");
        validateCS(null).thenRun(() -> System.out.println("Validation complete"));
        System.out.println("Exit");
    }

    public static CompletionStage<Void> validateCS(String val) {
        return CompletableFuture.runAsync(() -> {
            if (val == null) {
                System.out.println("Validation failed! value is null");
            }
        });
    }

    public static void validate(String val) {
        CompletableFuture.runAsync(() -> {
            if (val == null) {
                System.out.println("Validation failed! value is null");
            }
        });
    }
}

If you run tryWithVoid() , you will see that the output will be:

Started
Exit
Validation failed! value is null

Whereas, when you run tryWithCompletionStageVoid() , it will be:

Started
Validation failed! value is null
Validation complete
Exit

In first approach, tryWithVoid() , it is not waiting for the operation to complete. So the result of the operation may or may not be available for the code following the invocation.

However, in second approach tryWithCompletionStageVoid() , it waits for the stage to complete before running the next stage.

If you don't have dependency between the stages, you can go with void

The difference is that with returning a void you cannot do anything chained to the CompletableFuture after it finishes.

When you return CompletableFuture<Void> you can chain further things to it. For example invoke thenRun() , thenRunAsync() , or simply join() to wait for it to finish if needed.

So it depends on whether the caller cares about the status of the CompletableFuture and wants to apply further logic attached to it.

In your case, in Sample 1, doValidation() is running on a separate thread, and whatever happens to it is really irrelevant. service.performTask() will take place independently of it, and could even start and/or finish before doValidation() .

In Sample 2, doValidation() has to finish successfully before performTask() actually takes place. So it seems that this version is actually the correct one. Keep in mind that the main thread running CompletionStage<Response> performTask() will finish and return immediately after setting up the pipeline, the actual performValidation() could still be running (or could even not have been started yet).

By the way you can simplify the call as follows:

service.performTask(request).thenApply(this::buildMyResponse);

You don't have to wrap it in a CompletableFuture again just to unwrap it again with thenCompose() .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM