Reputation: 3522
In my app I have 3 future calls, that are done in parallel and when a response for one of them is received, I have another 3 requests that all should finish before proceeding with code execution, precisely the DeferredResult
from spring.
After a while, I realized that page is sometimes rendered before the latter 3 requests are done. Original source code (ommited logic for simplicity):
public DeferredResult<String> someControllerMethod() {
DeferredResult<String> result = new DeferredResult();
CompletableFuture.allOf(
future1(),
future2(),
future3()
)
.whenComplete((aVoid, throwable) -> result.setResult("something"));
return result;
}
public CompletableFuture<?> future3() {
return someService.asyncCall()
.thenApplyAsync(response -> {
....
return CompletableFuture.allOf(
future4(),
future5(),
future6()
);
}
);
}
With thenApplyAsync
sometimes DeferredResult
is completed before actual future, while changing to thenComposeAsync
seems solve the issue. Could someone explain me why? Or it's a bug in my code somewhere and it should not behave this way?
Upvotes: 0
Views: 855
Reputation: 298233
thenApply[Async]
accepts a function that evaluates to an arbitrary value. Once the value has been returned, the future will be completed with that value. When the function, like in your code, returns another future, this doesn’t add an additional meaning to it, the future will be the result value, whether completed or not, just like any other object.
In fact, your
public CompletableFuture<Void> future3() {
return someService.asyncCall()
.thenApplyAsync(response -> {
....
return CompletableFuture.allOf(
future4(),
future5(),
future6()
);
}
);
}
method does not even compile, as the result is CompletableFuture<CompletableFuture<Void>>
, a future whose result value is another future. The only way not to spot the error, is to use a broader type, e.g. CompletableFuture<Object>
or CompletableFuture<?>
, as the return type of future3()
.
In contrast, thenCompose[Async]
expects a function that evaluates to another future, to exactly the outcome you expect. That’s the fundamental different between “apply” and “compose”. If you keep the specific CompletableFuture<Void>
return type for future3()
, the compiler already guides you to use “compose”, as only that will be accepted.
public CompletableFuture<Void> future3() {
return someService.asyncCall()
.thenComposeAsync(response -> {
....
return CompletableFuture.allOf(
future4(),
future5(),
future6()
);
}
);
}
Upvotes: 2