How to combine 3 or more CompletionStages?
The only way to combine multiple stages that scales well with a growing number of stages, is to use CompletableFuture
. If your CompletionStage
s aren’t CompletableFuture
s you may still convert them using .toCompletableFuture()
:
CompletableFuture<A> aCompletionStage = getA().toCompletableFuture();
CompletableFuture<B> bCompletionStage = getB().toCompletableFuture();
CompletableFuture<C> cCompletionStage = getC().toCompletableFuture();
CompletableFuture<D> dCompletionStage = getD().toCompletableFuture();
CompletionStage<Combined> combinedDataCompletionStage = CompletableFuture.allOf(
aCompletionStage, bCompletionStage, cCompletionStage, dCompletionStage)
.thenApply(ignoredVoid -> combine(
aCompletionStage.join(), bCompletionStage.join(),
cCompletionStage.join(), dCompletionStage.join()) );
This contains more boilerplate than combining two stages via thenCombine
but the boilerplate doesn’t grow when adding more stages to it.
Note that even with your original thenCombine
approach, you don’t need a Triple
, a Pair
is sufficient:
CompletionStage<Combined> combinedDataCompletionStage =
aCompletionStage.thenCombine(bCompletionStage, (Pair::of)).thenCombine(
cCompletionStage.thenCombine(dCompletionStage, Pair::of),
(ab, cd) -> combine(ab.getLeft(), ab.getRight(), cd.getLeft(), cd.getRight()));
Still, it doesn’t scale well if you want to combine more stages.
An in-between solution (regarding complexity) might be:
CompletionStage<Combined> combinedDataCompletionStage = aCompletionStage.thenCompose(
a -> bCompletionStage.thenCompose(b -> cCompletionStage.thenCompose(
c -> dCompletionStage.thenApply(d -> combine(a, b, c, d)))));
That’s simpler in its structure but still doesn’t scale well with more more stages.
Holger's third answer can be made a little bit shorter:
CompletionStage<Combined> combinedDataCompletionStage = aCompletionStage.thenCompose(
a -> bCompletionStage.thenCompose(
b -> cCompletionStage.thenCombine(dCompletionStage,
(c, d) -> combine(a, b, c, d))));
Any number of CompletableFuture can be combined (reduced)
CompletionStage<A> futA = getA();
CompletionStage<B> futB = getB();
CompletionStage<C> futC = getC();
Stream.of(futA, futB, futC)
.reduce((f1, f2) -> f1.thenCombine(f2, (d1, d2) -> combine(d1, d2));
The implementation of combine method will be responsible to merge data values (A, B and C), which could be tricky if A, B and C are disparate.
You asked about "3 or more", if you have them in a List as CompletableFutures (see other answers) you could use this handy method:
private static <T> CompletableFuture<List<T>> join(List<CompletableFuture<T>> executionPromises) {
CompletableFuture<Void> joinedPromise = CompletableFuture.allOf(executionPromises.toArray(CompletableFuture[]::new));
return joinedPromise.thenApply(voit -> executionPromises.stream().map(CompletableFuture::join).collect(Collectors.toList()));
}
It converts your "list of futures" to a "future for a list of the results".