5

I was trying to understand CompletableFuture, and came across 2 methods, thenApplyAsync and thenCompose. Am trying to understand the difference between these two.

CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
    System.out.println(Thread.currentThread().getName() + " Printing hello");
    return "Hello";
}).thenCompose((String s) -> {
  return CompletableFuture.supplyAsync(() -> {
      System.out.println(Thread.currentThread().getName() + " Adding abc");
      return "abc "+s;});
}).thenApplyAsync((String s) -> {
    System.out.println(Thread.currentThread().getName() + " Adding world");
    return s + " World";
}).thenApplyAsync((String s) -> {
    System.out.println(Thread.currentThread().getName() + " Adding name");
    if (false) {
       throw new RuntimeException("Oh no exception");
    }
    return s + " player!";
}).handle((String s, Throwable t) -> {
    System.out.println(s != null ? s : "BLANK");
    System.out.println(t != null ? t.getMessage() : "BLANK Exception");
    return s != null ? s : t.getMessage();
});

How I interpreted these methods as, thenApplyAsync will execute the supplied function in a different thread and return a result, but internally it is wrapped inside a CompletionStage. Whereas, thenCompose will get the return a reference to a CompletionStage.

So under what scenarios, will I use thenCompose over thenApplyAsync?

Went through this link, but it was talking about thenApply, which is somewhat different: CompletableFuture | thenApply vs thenCompose

pedromss
  • 2,443
  • 18
  • 24
Himanshu Bhardwaj
  • 4,038
  • 3
  • 17
  • 36
  • 1
    There's very little difference between `thenApply` and `thenApplyAsync`, so the linked question should answer you quite well. – Kayaman Sep 05 '17 at 17:44
  • 1
    It seems you are assuming that the only thing you could do in the function passed to `thenCompose`, is to call `supplyAsync` so that the net result is similar to `thenApplyAsync`. Consider that you could do entirely different things in the function passed to `thenCompose` instead… – Holger Sep 05 '17 at 18:25
  • @JoeC he linked that question in his question. Plus this question is "thenApplyAsync vs thenCompose" different things – pedromss Sep 05 '17 at 20:31
  • 1
    Fundamentally, the answer is the same. The `thenCompose` method is really meant for APIs which return `CompletableFuture`s, whereas `thenApplyAsync` is meant for those which don't. – Joe C Sep 05 '17 at 20:32

1 Answers1

11

You would use thenCompose when you have an operation that returns a CompletionStage and thenApply when you have an operation that doesn't return a CompletionStage. -> This is was is in thenApply vs thenCompose

However the Async variants of the CompletionStage interface have subtle difference and rare use cases.

Let's take this example into account:

import java.util.concurrent.CompletableFuture;

public class Futures {

    public static void main(String[] args) throws InterruptedException {

        CompletableFuture<Void> c = CompletableFuture.runAsync(() -> {

            System.out.println("run1: " + Thread.currentThread().getId());
        });

        c.whenComplete((r, t) -> {
            System.out.println("I'm completed");
        });

        c.thenCompose(__ -> {
            System.out.println("thenCompose1: " + Thread.currentThread().getId());
            return CompletableFuture.runAsync(() -> {
                System.out.println("run2: " + Thread.currentThread().getId());
            });
        }).thenRunAsync(() -> {
            System.out.println("RunAsync1: " + Thread.currentThread().getId());
        });

        Thread.sleep(5000);

        System.out.println("Finished");
    }
}

Which outputs something like:

run1: 11
thenCompose1: 11
run2: 12
I'm completed
RunAsync1: 11
Finished

Notice that thenApplyAsync doesn't not affect the original future's completed state in contrast with the non async variants which do affect the completed state of the CompletionStage.

Use cases:

thenCompose

You might thenCompose when you have 2 asynchronous operations that need to be executed sequentially:

static CompletionStage<String> insert(Object database) {
    throw new UnsupportedOperationException();
}

static CompletionStage<Object> get(String id) {
    throw new UnsupportedOperationException();
}

public static void main(String[] args) throws InterruptedException {

    Object db = new Object(); // pretend this connects to a database
    CompletionStage<Object> recordInserted = insert(db).thenCompose(id -> get(id));
}

You can only retrieve the record after you inserted it.

Async variants of thenXXXX methods

Imagine for a moment that you have an application that allows users to register themselves and upon registration they will receive a confirmation email to confirm their account.

You don't want the user to be waiting for ever if the mail server is down or if it takes a long time to compose the email or perform additional checks.

You would then use thenApplyAsync to fire off the send email logic because it is not crucial to your system. A user can always go back and say "send me another email"

static CompletionStage<String> register(String username) {
    throw new UnsupportedOperationException();
}

static void sendConfirmationEmail(String username) {
    throw new UnsupportedOperationException();
}

public static void main(String[] args) throws InterruptedException {

    register("user").thenAcceptAsync(username -> sendConfirmationEmail(username));

}

Here your system will respond when the registration is complete but it will not wait for the email to be sent resulting in improved responsiveness of your system.

The uses cases I've found so far for the Async variants are scarce but they do exist.

pedromss
  • 2,443
  • 18
  • 24
  • Too late to comment on this. But I am not sure this is correct "**_`thenApply` when you have an operation that doesn't return a CompletionStage._**" My understanding is `thenApply` also returns Completion Stage – NIGAGA Aug 18 '19 at 05:59
  • It can, but then you'll end up with a `CompletionStage>` – pedromss Aug 19 '19 at 22:05
  • got it. So it will be correct to say `thenApply` does return CompletionStage but it can be nested like `CompletionStage>` depending on when thenApply has been called in the chain of CompletableFutures. – NIGAGA Aug 20 '19 at 23:22
  • Yes, but you rarely want to have a nested `CompletionStage` – pedromss Aug 21 '19 at 09:58
  • For me running your first piece of code is spawning 3 different threads. Also thenRunAsync should run on a new thread. `run1: 10 thenCompose1: 10 I'm completed run2: 11 RunAsync1: 12` @pedromss – amritdevilo Feb 03 '20 at 09:46