javaasynchronousexceptioncompletable-futureconditional-execution

Chaining completable futures based on conditions


I am having a bunch of methods that return a CompletableFuture and I would like to chain in a specific way

package com.sandbox;

import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.stream.IntStream;

public class SandboxFutures {

    public CompletableFuture<Integer> generateRandom(int min, int max) {
        return CompletableFuture.supplyAsync(() -> {
            if (min >= max) {
                throw new IllegalArgumentException("max must be greater than min");
            }

            Random r = new Random();
            return r.nextInt((max - min) + 1) + min;
        });
    }

    public CompletableFuture<String> printEvenOrOdd(int result) {
        return CompletableFuture.supplyAsync(() -> {
            if (result % 2 == 0)
                return "Even";
            else
                return "Odd";
        });
    }

    public CompletableFuture<Integer> findFactorial(int evenNumber) {
        return CompletableFuture.supplyAsync(() -> {
            if (evenNumber <= 0) {
                return 0;
            }

            return IntStream.rangeClosed(2, evenNumber).reduce(1, (x,y) -> x*y);
        });
    }

    public CompletableFuture<Integer> convertToNearestEvenInteger(int oddNumber) {
        return CompletableFuture.supplyAsync(() -> {
           if (oddNumber <= 0) {
               return 2;
           }
           return oddNumber+1;
        });
    }

}

I am trying to combine them based on the following rules,

  1. Generate a random number between 1 and 100
  2. If the number is even print Even, if it is odd print Odd
  3. If the number is even call the findFactorial with the random number
  4. If the number is odd find the nearest even via convertToNearestEvenInteger

I am not too clear on how to do the conditional chaining and exception handling. Some examples or code snippets may be helpful.


Solution

  • The way printEvenOrOdd is written makes it more difficult than it needs to be. The problem is that it doesn't print the word "Even" or "Odd", it returns it, which means the original result is lost. The rest of the steps rely on having the actual number. To work around it, you could use call printEvenOrOdd and use .thenApply(__ -> result) to restore the original number afterwards. It would look like this:

    System.out.println(
        generateRandom(1, 100)
            .thenCompose(result ->
                printEvenOrOdd(result)
                    .thenAccept(System.out::println)
                    .thenApply(__ -> result)
            )
            .thenCompose(result ->
                result % 2 == 0
                    ? findFactorial(result)
                    : convertToNearestEvenInteger(result)
            )
            .join()
    );
    

    A better solution would be to change the definition of printEvenOrOdd to something like:

    public CompletableFuture<Integer> printEvenOrOdd(int result) {
        return CompletableFuture.supplyAsync(() -> {
            System.out.println(result % 2 == 0 ? "Even" : "Odd");
            return result;
        });
    }
    

    That would make it much easier to chain steps 3 and 4:

    System.out.println(
        generateRandom(1, 100)
            .thenApply(this::printEvenOrOdd)
            .thenCompose(result ->
                result % 2 == 0
                    ? findFactorial(result)
                    : convertToNearestEvenInteger(result)
            )
            .join()
    );