简体   繁体   中英

Java CompletableFuture: Avoid callback hell

I have a question regarding CompletableFuture in Java. I'm waiting for a CompletableFuture to complete and depending on the received result, I want to either call a new task and wait for the CompletableFuture to complete or do something different. I'm not happy with my solution since there are so many callbacks and it is different to read. Could you help my to improve my code?

final CompletableFuture<String> future = new CompletableFuture<>();

final ActorRef processCheckActor = actorSystem.actorOf(
    springExtension.props("ProcessCheckActor"), "processCheckActor-" + new Random().nextInt());

final CompletableFuture<Object> checkResponse =
    PatternsCS.ask(processCheckActor, new ProcessToCheckMessage(processId), TIMEOUT)
        .toCompletableFuture();

checkResponse.thenAccept(obj -> {
  final ProcessCheckResponseMessage msg = (ProcessCheckResponseMessage) obj;
  if (msg.isCorrect()) {
    final CompletableFuture<Object> response =
        PatternsCS.ask(processSupervisorActor, new ProcessStartMessage(processId), TIMEOUT)
            .toCompletableFuture();

    response.thenAccept(obj2 -> {
      future.complete("yes");
    });
  } else {
    future.complete("no");
  }
});

First of all, you should avoid creating a CompletableFuture<Object> . The generic type should be the type your function returns ( CompletableFuture<ProcessCheckResponseMessage> in your case). That way you don't need the cast.

I would suggest using thenApply rather than thenAccept . This will create the second CompletableFuture for you, meaning you no longer need the declaration in the first line.

Finally, as a general rule, you should think twice about multi-line lambdas, and definitely avoid nested lambdas. You should consider creating a new method for these lambdas instead.

My 2 cents with sample code to help your callback scenario.

I wrote 3 functions: testFunction , getResponseMessage and getResponseString .

  • testFunction is the main function.
  • getResponseMessage is chained to main testFunction through thenApply
  • getResponseString is chained at the end through thenCompose .

The trick is to chain multiple smaller functions through the higher order functions like thenApply , thenCompose , thenCombine etc.

public CompletableFuture<String> testFunction() {

    Future<Object> fut = Patterns.ask(getContext().actorSelection("actor1"), new ProcessToCheckMessage(1), 10);

    return FutureConverters.toJava(fut).toCompletableFuture()

            .thenApply(obj -> getResponseMessage(obj))

            .thenCompose(processCheckResponseMessage -> getResponseString(processCheckResponseMessage));
}

public ProcessCheckResponseMessage getResponseMessage(Object obj) {
    if (obj instanceof ProcessCheckResponseMessage) {
        return (ProcessCheckResponseMessage) obj;
    } else {
        throw new RuntimeException("unexpected data");
    }
}

public CompletableFuture<String> getResponseString(ProcessCheckResponseMessage processCheckResponseMessage) {
    if (processCheckResponseMessage.isCorrect()) {
        Future<Object> rest = Patterns.ask(getContext().actorSelection("actor2"), new ProcessStartMessage(1), 10);
        return FutureConverters.toJava(rest).toCompletableFuture().thenApply(obj -> "yes");
    } else {
        return CompletableFuture.completedFuture("no");
    }
}

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