How do you access completed futures passed to CompletableFuture allOf? - java

I am trying to get a grip of Java 8 CompletableFuture. How can I join these to person and return them after "allOf". The code under is not working but gives you an idea of what I have tried.
In javascript ES6 i would do
Promise.all([p1, p2]).then(function(persons) {
console.log(persons[0]); // p1 return value
console.log(persons[1]); // p2 return value
});
My efforts in Java so far
public class Person {
private final String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
#Test
public void combinePersons() throws ExecutionException, InterruptedException {
CompletableFuture<Person> p1 = CompletableFuture.supplyAsync(() -> {
return new Person("p1");
});
CompletableFuture<Person> p2 = CompletableFuture.supplyAsync(() -> {
return new Person("p1");
});
CompletableFuture.allOf(p1, p2).thenAccept(it -> System.out.println(it));
}

The CompletableFuture#allOf method does not expose the collection of completed CompletableFuture instances that were passed to it.
Returns a new CompletableFuture that is completed when all of the
given CompletableFutures complete. If any of the given
CompletableFutures complete exceptionally, then the returned
CompletableFuture also does so, with a CompletionException holding
this exception as its cause. Otherwise, the results, if any, of the
given CompletableFutures are not reflected in the returned
CompletableFuture, but may be obtained by inspecting them
individually. If no CompletableFutures are provided, returns a
CompletableFuture completed with the value null.
Note that allOf also considers futures that were completed exceptionally as completed. So you won't always have a Person to work with. You might actually have an exception/throwable.
If you know the amount of CompletableFutures you're working with, use them directly
CompletableFuture.allOf(p1, p2).thenAccept(it -> {
Person person1 = p1.join();
Person person2 = p2.join();
});
If you don't know how many you have (you're working with an array or list), just capture the array you pass to allOf
// make sure not to change the contents of this array
CompletableFuture<Person>[] persons = new CompletableFuture[] { p1, p2 };
CompletableFuture.allOf(persons).thenAccept(ignore -> {
for (int i = 0; i < persons.length; i++ ) {
Person current = persons[i].join();
}
});
If you wanted your combinePersons method (ignoring it's a #Test for now) to return a Person[] containing all the Person objects from the completed futures, you could do
#Test
public Person[] combinePersons() throws Exception {
CompletableFuture<Person> p1 = CompletableFuture.supplyAsync(() -> {
return new Person("p1");
});
CompletableFuture<Person> p2 = CompletableFuture.supplyAsync(() -> {
return new Person("p1");
});
// make sure not to change the contents of this array
CompletableFuture<Person>[] persons = new CompletableFuture[] { p1, p2 };
// this will throw an exception if any of the futures complete exceptionally
CompletableFuture.allOf(persons).join();
return Arrays.stream(persons).map(CompletableFuture::join).toArray(Person[]::new);
}

As pointed out in #Sotirios Delimanolis's answer, the CompletableFuture is not exception friendly, which means we cannot get the result of all futures easily when exceptions happen in one or several of the futures.
As OP's question is not limited to the successful situation, in this answer, I want to make an addition to this imperfect of CompletableFuture.
If we however want to know the results of all the futures, we can handle this separately when defining futures. For example:
CompletableFuture<Person> p1 = CompletableFuture.supplyAsync(() -> {
return new Person("p1");
});
p1.thenAccept(person -> {
// handle successful future
// for example: add p1 to success operation list
});
p1.exceptionally((exception) -> {
// handle fail future
// for example, log the parameter (not exist in this case) to create the person
System.out.println("Exception happen in creating p1");
return null;
});
and then after calling CompletableFuture.allOf(persons).join() (Please also pay attention to the exception handling here), we can figure out which future succeeds and which future fails.
Hope this simple tip can help newcomers write solid code in real-world business systems.

CompletableFuture.allOf(p1, p2); // .get can be ignored
List<Person> res =
List.of(p1, p2) // list of futures
.stream()
.map(future -> {
System.out.println("future " + future.join());
return future.join();
})
.collect(Collectors.toList());
Or you can fetch the values individually, using p1.get() and p2.get()

Related

Transform a CompletableFuture of a list of objects to another CompletableFuture of a list of objects

I have a method (getIds) that returns a CompletableFuture<List<String>>. For each of the IDs in the list it returns, I need to call a method that fetches the corresponding username for that ID. That fetching takes some time, so it returns a CompletableFuture of String. Ultimately the method should return a CompletableFuture of the usernames List<String>:
public CompletableFuture<List<String>> getUsernames() {
CompletableFuture<List<String>> future = getIds();
return future.thenCompose(ids -> {
return CompletableFuture.allOf(ids.stream()
.map(this::getUsername)
...);
});
private CompletableFuture<String> getUsername(String id) {
...
}
Can anyone advise how to achieve this?
The BetterFuture library I wrote some time ago makes this quite easy:
public BetterFuture<List<String>> getUsernames() {
BetterFuture<List<String>> future = getIds();
return future.andThen(ids ->
BetterFuture.reduce(ids.stream().map(this::getUsername))
.map(Stream::toList));
}
BetterFuture.reduce reduces a Stream<BetterFuture<T>> to a BetterFuture<Stream<T>> and acts in parallel on the stream of futures it receives as an argument.
Since BetterFuture is just thin wrapper around CompletableFuture, it shouldn't be too hard to peel the the relevant implementation out and make above snippet work directly on CompletableFuture.

How to do multiple API calls concurrently in Spring service without changing main?

I need to make a service in an existing fat code to get results from 4 APIs and I need to merge them and reformat each responses, but it takes very slow due to 4 calls that I don't know how to do it concurrently. I am also unable to change the main to add Runnable or such executor in the main as it may have snowballing effect to another code.
So currently, I have made a controller which handle the request, a service which get the request from user and call 5 different service-middleware (SM) functions. Every SM functions used to call an external API, and in every SM, I reformat each return map of the APIs there as well. I use java.net.HttpURLConnection to do the API calls. Thus, I got my API "worked" but can't be faster than 4 seconds. Those APIs needs additional OAuth, so it would be roughly 10 API calls in total.
Since the current returns of API calls are Object type, I can treat it as Map, and reformat the output by doing looping for the data inside it. So the SM function would likely have the code similarly to below:
token = sendHttpRequest(authUrl, authRequestHeader, null, null, "GET");
Map response = sendHttpRequest(url, requestHeader, bodyParam, null, "POST");
List<Map> data = (List) ((Map) response.get("output")).get("data");
List<Map> result = new HashMap();
for(Map m : data) {
Map temp = new HashMap();
temp.put("name", m.get("Name"));
temp.put("health_status", m.get("HealthStatus"));
result.add(temp);
}
// This format is mandatory
Map finalResult = new HashMap();
finalResult.put("output", result);
finalResult.put("status", "OK");
return finalResult;
And the sendHttpRequest is the method to send request, serializing params to JSON and deserializing API output to be an Object. Here's the sendHttpRequest look like:
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(csf)
.build();
HttpComponentsClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory();
requestFactory.setConnectTimeout(this.connectTimeOut);
requestFactory.setReadTimeout(this.readTimeOut);
requestFactory.setHttpClient(httpClient);
RestTemplate rt = new RestTemplate(requestFactory);
HttpEntity<Map> request = null;
if(method.equals("POST"))
request = new HttpEntity<Map>(objBody, headers);
else if(method.equals("GET"))
request = new HttpEntity<Map>(headers);
try {
ResponseEntity<Map> response = null;
if(method.equals("POST"))
restTemplate.postForEntity(url, request , Map.class);
if(method.equals("GET"))
restTemplate.postForEntity(url, request , Map.class);
if(this.outputStream){
logger.debug("Output : " + response.getBody());
}
return response.getBody();
} catch(HttpClientErrorException e) {
logger.debug(e.getMessage());
}
The sendHttpRequest method is also an existing method that I am disallowed to change except if I just make a new method for doing my requests only.
Simply say, here's the things I need to do:
For each of the API calls:
Get the Authorization token from an external API.
Do the request (POST/GET) to another external API to get data.
Reformat the data to be expected format for response (each has its own format) <Mostly loop the array of the response object to remap the field names as it's necessary>.
After all APIs finished calling, I need to do:
Merge output from API 1 and 3 to a Map/Object
Merge output from API 2 & 4 to an Array and sort them all
Put response from API 5 in the inner object of a defined attribute/field.
Things I had tried
I had tried the use of ExecutorCompletionService to call the 5 SMs. I also created an inner class that implements Callable for this.
ExecutorService executor = Executors.newFixedThreadPool(5);
CompletionService completionService = new ExecutorCompletionService<>(executor);
List<Future<Map>> results = new ArrayList<>();
for(int i=1; i<6; i++) {
// i here is used to define which api calls to be done
results.add(completionService.submit(new CallAPIClass(paramMap, i)));
}
for (int i=0; i < results.size(); i++) {
try {
Map result = (Map) completionService.take().get();
int code = (int) result.get("code");
// Collect the results for each SM (SM function has described above)
} catch (Exception e) {
logger.debug(e.getMessage());
}
}
// Merge the outputs.
In the Merge the outputs, I need to construct the map, so it will be like this:
{
"details": {"api1": {...}, "api3": {...}},
"list_items": [{...}, {...}, ...], // Results of sorted merged lists from api2 & api4
"api5": [{...}, {...}, {...}, ...]
}
Meanwhile, from the api responses, basically I just retrieve all of their output_schema when exists.
Any tips to optimize and speed up this API call, so by the same number of calls, this can be executed faster??? Any help is greatly appreciated.
Edit
I have read #Ananthapadmanabhan's answer, but it seems that I need to change the main class file which I can't do. Or is it actually possible to apply the use of CompletableFuture without using #EnableAsync in main class?
I also wonder how to get this done in a faster time even with CompletableFuture and EnableAsync with this chain of processes.
The solution you tried looks quite decent to me:
ExecutorService executor = Executors.newFixedThreadPool(5);
CompletionService completionService = new ExecutorCompletionService<>(executor);
List<Future<Map>> results = new ArrayList<>();
for(int i=1; i<6; i++) {
// i here is used to define which api calls to be done
results.add(completionService.submit(new CallAPIClass(paramMap, i)));
}
for (int i=0; i < results.size(); i++) {
try {
Map result = (Map) completionService.take().get();
int code = (int) result.get("code");
// Collect the results for each SM (SM function has described above)
} catch (Exception e) {
logger.debug(e.getMessage());
}
}
// Merge the outputs.
I am not quite sure if, in addition to a probably more fluent API, using CompletableFuture will give you any benefit related to the performance of the program - the subject has been broadly discussed here in SO, see for example 1 2 3 - but here it is a possible solution.
In fact, the next code is based in one of my previous answers, in turn closely related to this article from the Tomasz Nurkiewicz blog.
The CompletableFuture counterpart for the code you provided will look like:
ExecutorService executor = Executors.newFixedThreadPool(5);
// List of the different parameters to perform every external API invocations
final List<Map> smParameters = Arrays.asList(
...
);
// Submit invoke external task to the thread pool
final List<CompletableFuture<Map>> futures = smParameters.stream().
map(paramMap -> CompletableFuture.supplyAsync(() -> invokeExternalAPI(paramMap), executor)).
collect(Collectors.<CompletableFuture<Map>>toList())
;
// The next code is based on the sequence method proposed in the blog I cited
// The idea is to turn the `List<CompletableFuture<Map>>` we have into a
// CompletableFuture<List<Map>> with the results of every single async task
final CompletableFuture<Void> allDoneFuture =
CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
final CompletableFuture<List<Map>> allDone = allDoneFuture.thenApply(v ->
futures.stream().
map(future -> future.join()).
collect(Collectors.<Map>toList())
);
// Merge the outputs.
final Map result = allDone.thenAccept(results ->
// Merge the outputs. The results variable contains the different Mapz
// obtained from the every different API invocation
);
Please, verify the above code, probably it will require the definition of the type of the different parameters of your Map objects.
The mentioned invokeExternalAPI could accept a Map with the different parameters required to perform the individual APIs invocations, something like:
private Map invokeExternalAPI(Map configuration) {
// Pass and extract from the configuration the authUrl, etcetera, everything you need to
// Your code...
token = sendHttpRequest(authUrl, authRequestHeader, null, null, "GET");
Map response = sendHttpRequest(url, requestHeader, bodyParam, null, "POST");
List<Map> data = (List) ((Map) response.get("output")).get("data");
List<Map> result = new HashMap();
for(Map m : data) {
Map temp = new HashMap();
temp.put("name", m.get("Name"));
temp.put("health_status", m.get("HealthStatus"));
result.add(temp);
}
// This format is mandatory
Map finalResult = new HashMap();
finalResult.put("output", result);
finalResult.put("status", "OK");
return finalResult;
}
I think you don't need to modify your main class nor any configuration, as the solution is pure Java based.
Please, bear in mind that this generic approach can be customized to accommodate different requirements.
For example, according to your comments, it seems that you need to invoke from your service the functionality implemented in your different services middleware.
In order to define the list of tasks you wanna perform concurrently you could try something like the following instead of my initial suggestion:
List<CompletableFuture<Map>> futures = new ArrayList<>(5);
// Obtain a reference to the second middleware, and submit it
final ServiceMiddleware1 sm1 = new ServiceMiddleware1();
final CompletableFuture<Map> sm1Cf = CompletableFuture.supplyAsync(() -> sm1.doYourStuff(), executor);
futures.add(sm1Cf);
// Now obtain a reference to the second middleware, and submit it again
final ServiceMiddleware2 sm2 = new ServiceMiddleware2();
final CompletableFuture<Map> sm2Cf = CompletableFuture.supplyAsync(() -> sm2.doYourStuff(), executor);
futures.add(sm2Cf);
// the rest of service middleware. I think here a common interface
// or some kind of inheritance could be of help in the invocation
// At the end, you will get the list of futures you wanna execute in parallel
// The rest of the code is the same
final CompletableFuture<Void> allDoneFuture =
CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
final CompletableFuture<List<Map>> allDone = allDoneFuture.thenApply(v ->
futures.stream().
map(future -> future.join()).
collect(Collectors.<Map>toList())
);
// Merge the outputs.
final Map result = allDone.thenAccept(results ->
// Merge the outputs. The results variable contains the different Mapz
// obtained from the every different API invocation
);
To deal with errors you have several options.
One obvious is to handle the error in the service middleware itself, in such a way that it will never raise any exceptions, but return some kind of information in its result Map like result code, status, etcetera.
CompletableFuture itself gives you different options as well to deal with errors. As you probably need to perform some changes in the result Map you can, when necessary, use the handle method. It basically takes as argument the result and the hypothetical exception obtained in the execution of the task associated with a CompletableFuture, and return a new one CompletableFuture with the appropriate customizations, based on that result and possible error. For example, in your 4th and 5th service middlewares, which seems to raise errors, you can use something like:
final ServiceMiddleware4 sm4 = new ServiceMiddleware4();
final CompletableFuture<Map> sm4Cf = CompletableFuture.supplyAsync(() -> sm4.doYourStuff(), executor)
.handle((result, exception) -> {
if (exception == null) {
return result;
}
Map actualResult = new HashMap();
actualResult.put("errorCode", "xxx")
actualResult.put("errorMessage", exception.getMessage());
return actualResult;
});
)
;
futures.add(sm4Cf);
This great article, for instance, explains in detail further error handling approaches.
All these approaches assume that your code doesn't throw checked exceptions. If you need to deal with them, as it seems according to your comment, you could use a modified version of the code that Holger posted in this SO answer. The idea is to create a method that will handle the check exception, completing it with the appropriate error if necessary:
public static <T> CompletableFuture<T> supplyAsync(Supplier supplier, Executor executor) {
CompletableFuture<T> f=new CompletableFuture<>();
CompletableFuture.runAsync(() -> {
try { f.complete(supplier.get()); } catch(Throwable t) { f.completeExceptionally(t); }
}, executor);
return f;
}
Then, use this method to submit every service middleware task:
List<CompletableFuture<Map>> futures = new ArrayList<>(5);
// Obtain a reference to the second middleware, and submit it
final ServiceMiddleware1 sm1 = new ServiceMiddleware1();
final CompletableFuture<Map> sm1Cf = supplyAsync(() -> sm1.doYourStuff(), executor)
// this method will only be executed if any exception is thrown
.exceptionally(exception -> {
Map errorResult = new HashMap();
errorResult.put("errorCode", "xxx")
errorResult.put("errorMessage", exception.getMessage());
return errorResult;
});
futures.add(sm1Cf);
// Apply a similar logic to the rest of services middlewares...
// The rest of the code is the same as above
final CompletableFuture<Void> allDoneFuture =
CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
final CompletableFuture<List<Map>> allDone = allDoneFuture.thenApply(v ->
futures.stream().
map(future -> future.join()).
collect(Collectors.<Map>toList())
);
// Merge the outputs.
// Please, be aware that in the lambda expression results
// is a List of the different Maps obtained as the individual
// results of every single service middleware call
// I would create an object that agglutinates these results in
// the right format, as you indicated in your question. Let's call
// this container class ServiceMiddlewareResult. Then, the merge
// results code will looks like similar to this
final ServiceMiddlewareResult result = allDone.thenAccept(results -> {
ServiceMiddlewareResult serviceMiddlewareResult = new ServiceMiddlewareResult();
// Variable used for storing temporarily the Api 2 and 4 results
// Parameterize it as necessary
List tempResultsFromApi2AndApi4 = new ArrayList();
// Honestly I don't remember if the order of the results is the
// same as the order of the futures that generated them, my guess
// is that not, as it depends on the actual future completion,
// but in any way I always try thinking that the results can be
// in any order, so it is important that every Map contains the
// minimal information to identify the corresponding service
// middleware. With that assumption in mind, your code will look
// similar to this:
results.forEach(result -> {
// The suggested idea, identify the service middleware that
// produced the results
String serviceMiddleware = result.get("serviceMiddleware");
switch(serviceMiddleware) {
// handle every case appropriately
case 'sm1': {
// it should be similar to sm3
serviceMiddlewareResult.getDetails().setApi1(...);
break;
}
case 'sm2':
case 'sm4': {
// Extract results from the Map, and add to the temporary list
tempResultsFromApi2AndApi4.add(...)
break;
}
case 'sm5': {
// extract results and populate corresponding object
serviceMiddlewareResult.setApi5(...);
break;
}
}
});
List sortedResultsFromApi2AndApi4 = Collections.sort(
sortedResultsFromApi2AndApi4, ... the appropriate comparator...
);
result.setListItems(sortedResultsFromApi2AndApi4);
return result;
});
I modified the example to provide a posible approach to merge your results.
Please, consider include logging information within your service middleware code if you need to trace and improve the debugging capabilities offered by the overall solution.
If you have used them before, as an alternative, you could try solutions based in libraries like RxJava or Project Reactor as well.
If all the 4 api calls are independent of each other and you are using java 8 , you could extract them to separate functions in a separate service layer if needed and use spring #Async annotation on the method along with CompletableFuture as return type to make parallel calls.
#Service
public class TestClient {
RestTemplate restTemplate = new RestTemplate();
#Async
public CompletableFuture<List<TestPojo>> getTestPojoByLanguage(String language) {
String url = "https://test.eu/rest/v2/lang/" + language + "?fields=name";
Country[] response = restTemplate.getForObject(url, Country[].class);
return CompletableFuture.completedFuture(Arrays.asList(response));
}
#Async
public CompletableFuture<List<TestPojo>> getCountriesByRegion(String region) {
String url = "https://testurl.eu/rest/v2/region/" + region + "?fields=name";
Country[] response = restTemplate.getForObject(url, Country[].class);
return CompletableFuture.completedFuture(Arrays.asList(response));
}
}
Completable Future guide.

Concurrent hash map remove complex values

I have HashMap:
private final ConcurrentHashMap<String, List<Client>> clients;
And class:
public static class Client {
private String name; // it is also the key of the map
private String url;
}
From several threads, I call thread-safe method "removeElement" which has to remove one value from the list.
#Override
public CompletableFuture<Void> removeClient(Client client) {
return CompletableFuture.runAsync(() ->
clients.entrySet().removeIf(v ->
v.getValue().removeIf(
it -> client.url.equals(it.url))
)
);
}
But of course, it does not work. When I had got Method threw 'java.lang.UnsupportedOperationException' exception I resolved the issue like that:
#Override
public CompletableFuture<Void> removeClient(Client client) {
return CompletableFuture.runAsync(() -> {
List<Client> currentClients = new ArrayList<>(clients.get(client.getName()));
currentClients.remove(client);
if (currentClients.isEmpty()) {
clients.remove(client.getName());
} else {
clients.put(client.getName(), currentClients);
}
}
);
}
But it is not thread-safe. How can I achieve it here? Maybe there are more elegant ways to resolve it?
I think you could use ConcurrentHashMap::computeIfPresent in this case assuming that the same List instances are not put for the same keys :
CompletableFuture.runAsync(() -> {
clients.computeIfPresent(client.getName(), (name, clients1) -> {
List<Client> currentClients = new ArrayList<>(clients1);
currentClients.remove(client);
return currentClients.isEmpty() ? null : currentClients;
});
});
Since computeIfPresent is performed atomically and we are using copy of the list inside remappingFunction - it should work.
As we can read in the docs :
If the value for the specified key is present, attempts to compute a new mapping given the key and its current mapped value. The entire method invocation is performed atomically. Some attempted update operations on this map by other threads may be blocked while computation is in progress, so the computation should be short and simple, and must not attempt to update any other mappings of this map.

Nesting CompletionStages in Java to make inner blocks run before outer blocks

I've written a method like below:
public static CompletionStage<Tuple2<ObjectNode, String>> calculateTemplateTreeAndKeys(
String content,
RequestContext context,
MetricsClient metricsClient,
JdbcSession jdbcSession) {
AtomicReference<ObjectNode> templateTreeHolder = new AtomicReference<>();
templateTreeHolder.set(Json.rootNode());
return getTemplateIds(context, metricsClient, jdbcSession, content)
.thenCompose(
templateIds -> {
templateIds.map(
id ->
// do something and return CompletionStage<String>
.thenAccept(
tree -> {
templateTreeHolder.set(
(ObjectNode)
templateTreeHolder.get().set(id, Json.readTree(tree)));
System.out.println(
"From inner function: " + templateTreeHolder.get());
}));
return CompletableFuture.completedFuture(NotUsed.getInstance());
})
.thenApply(
notUsed -> {
String includedTemplateIdsStr =
getKeysFromTemplateTree(templateTreeHolder.get()).toJavaList().toString();
System.out.println("From outer function: " + templateTreeHolder.get());
return Tuple.of(templateTreeHolder.get(), includedTemplateIdsStr);
});
I expect the inner block to process and update templateTreeHolder before
.thenApply is invoked, so that templateTreeHolder would hold correct data to return. But, .thenApply block is processing before the inner .thenAccept block.
From the console output sequence:
From outer function: {}
From inner function: {"f9406341-c62a-411a-9389-00a62bd63629":{}}
I'm not sure what I'm doing wrong on chaining CompletionStages, kindly advise me how can I make sure that the inner block completes before the outer block?
Your function passed to thenCompose is returning an already complete future, i.e. return CompletableFuture.completedFuture(NotUsed.getInstance()); which allows the dependent stages to proceed immediately. This seem to conflict with the evaluation of the function passed to templateIds.map(…), which happens asynchronously, apparently.
Generally, you should avoid such mixture of completion stages and dependencies to side effects, especially when their asynchronous evaluation is not modeled as prerequisite completion stage.
But you can work-around this, if you have no other choice:
return getTemplateIds(context, metricsClient, jdbcSession, content)
.thenCompose(
templateIds -> {
// create an initially uncompleted stage
CompletableFuture<Object> subStage = new CompletableFuture<>();
templateIds.map(
id ->
// do something and return CompletionStage<String>
.thenAccept(
tree -> {
templateTreeHolder.set(
(ObjectNode)
templateTreeHolder.get().set(id, Json.readTree(tree)));
System.out.println(
"From inner function: " + templateTreeHolder.get());
// complete when all work has been done
subStage.complete(null);
}));
// use this stage for dependent actions
return subStage;
})
.thenApply(
notUsed -> {
String includedTemplateIdsStr =
getKeysFromTemplateTree(templateTreeHolder.get()).toJavaList().toString();
System.out.println("From outer function: " + templateTreeHolder.get());
return Tuple.of(templateTreeHolder.get(), includedTemplateIdsStr);
});
In the code above, the future will never get completed if your action fails with an exception before the completion attempt. The general pattern would be like this:
CompletableFuture<Type> stage = new CompletableFuture<>();
…
try {
code that will eventually call complete on stage
}
catch(Throwable t) {
stage.completeExceptionally(t);
}
But, of course, it will get a bit more complicated when the code supposed to complete the stage also bears asynchronous processing, so you have to guard the code trying to submit the actual completion code, as well as the actual completion code.
So, a more elaborated version of the inner code would look like:
CompletableFuture<Object> subStage = new CompletableFuture<>();
try {
templateIds.map(
id ->
// do something and return CompletionStage<String>
.thenAccept(
tree -> {
templateTreeHolder.set(
(ObjectNode)
templateTreeHolder.get().set(id, Json.readTree(tree)));
System.out.println(
"From inner function: " + templateTreeHolder.get());
})
.whenComplete((v,t) -> {
// complete when all work has been done
if(t != null) subStage.completeExceptionally(t);
else subStage.complete(v);
}));
} catch(Throwable t) {
subStage.completeExceptionally(t);
}
// use this stage for dependent actions
return subStage;
(perhaps, the “do something and return CompletionStage” has to be guarded with try { … } catch(Throwable t) { subStage.completeExceptionally(t); } too)

Java short circuit CompletableFuture

I am trying to find a way to skip CompletableFuture based on specific conditions.
For example
public CompletableFuture<Void> delete(Long id) {
CompletableFuture<T> preFetchCf = get(id);
CompletableFuture<Boolean> cf1 = execute();
/*This is where I want different execution path, if result of this future is true go further, else do not*/
// Execute this only if result of cf1 is true
CompletableFuture<T> deleteCf = _delete(id);
// Execute this only if result of cf1 is true
CompletableFuture<T> postDeleteProcess = postDelete(id);
}
What is a good way to achieve this ?
I will prepare a different example than the one you used in the question, because your code is not quite clear in intent from the readers perspective.
First suppose the existing of a CompletableFuture<String> that provides the name of a Star Wars characters.
CompletableFuture<String> character = CompletableFuture.completedFuture("Luke");
Now, imagine I have two other CompletableFuture that represent different paths I may want to follow depending on whether the first completable future provides a character that is a Jedi.
Supplier<CompletableFuture<String>> thunk1 = () -> CompletableFuture.completedFuture("This guy is a Jedi");
Supplier<CompletableFuture<String>> thunk2 = () -> CompletableFuture.completedFuture("This guy is not a Jedi");
Notice that I wrapped the CompletableFuture in a a Supplier, to avoid that they get eagerly evaluated (this is concept known as thunk).
Now, I go and to my asynchronous chain:
character.thenApply(c -> isJedi(c))
.thenCompose(isJedi -> isJedi ? thunk1.get() : thunk2.get())
.whenComplete((answer, error) -> System.out.println(answer));
The use of thenCompose let me choose a path based on the boolean result. There I evaluate one of the thunks and cause it to create a new CompletableFuture for the path I care about.
This will print to the screen "This guys is a Jedi".
So, I believe what you're looking for is the thenCompose method.
Not sure if I understand your objective, but why won't you just go with future chaining like you said in the comment? Something like this, just to illustrate:
public class AppTest {
#Test
public void testCompletableFutures() {
Integer id = (int) Math.random() * 1000;
CompletableFuture<Void> testing = AppTest.execute()
.thenAcceptAsync(result -> {
System.out.println("Result is: " + result);
if(result)
AppTest.delete(id);
else
throw new RuntimeException("Execution failed");
})
.thenApplyAsync(result -> AppTest.postDelete())
.thenAcceptAsync(postDeleteResult -> {
if(postDeleteResult)
System.out.println("Post delete cleanup success");
else
throw new RuntimeException("Post delete failed");
});
}
private static boolean postDelete() {
System.out.println("Post delete cleanup");
return Math.random() > 0.3;
}
private static CompletableFuture<Boolean> delete(int i) {
System.out.println("Deleting id = " + i);
return CompletableFuture.completedFuture(true);
}
private static CompletableFuture<Boolean> execute() {
return CompletableFuture.supplyAsync(() -> Math.random() > 0.5);
}
}
Of course that doesn't make much real-life sense, but I think it works to show a concept.
If you want to skip the second call after execute based on the result it's clearly not possible since you need that result. The point is that it should not matter for you whether you skipped that or not since it's asynchronous, you are not blocking to wait for that result.

Categories

Resources