Good morning,
I'm not quite getting the hang of CompletableFutures (I am an experienced developer, but I am not finding them particularly intuitive!).
Given the following snippet:
public CompletionStage<Void> leaveGame(GameService gameService)
{
return gameService.deregister(playerName)
.exceptionally(t -> {
LOGGER.info("Could not deregister: {}", t.getMessage());
throw new CompletionException(t);
});
}
which is called by the unit test:
#Test
public void shouldCompleteExceptionallyForFailedLeave()
{
var failFlow = new CompletableFuture<Void>();
failFlow.completeExceptionally(new Exception("TestNonExistentPlayer"));
when(mockedGameService.deregister(any(String.class))).thenReturn(failFlow);
try
{
player.leaveGame(mockedGameService).toCompletableFuture().get();
fail("Exception should have been thrown");
}
catch (Exception e)
{
assertEquals(Exception.class, e.getCause().getClass());
}
verify(mockedGameService, times(1)).deregister(any(String.class));
}
which mocks gameService.deregister(...) to completeExceptionally and return Exception.
In the above case, as expected, the exceptionally branch is triggered, the message is logged, and the exception in the unit test is caught, i.e. the fail(...) assertion is not triggered.
However, when I want to run a CompletionStage before leave game, e.g.:
public CompletionStage<Void> leaveGame(GameService gameService)
{
return CompletableFuture.runAsync(() -> System.out.println("BLAH"))
.thenRun(() -> gameService.deregister(playerName)
.exceptionally(t -> {
LOGGER.info("Could not deregister: {}", t.getMessage());
throw new CompletionException(t);
}));
}
The exceptionally branch is still triggered, but the exception is now not caught by the test method, i.e. the fail(...) assertion is triggered.
What am I doing wrong?
Many thanks in advance!
With your original definition
public CompletionStage<Void> leaveGame(GameService gameService)
{
return gameService.deregister(playerName)
.exceptionally(t -> {
LOGGER.info("Could not deregister: {}", t.getMessage());
throw new CompletionException(t);
});
}
The method leaveGame did not throw an exception but always returned a future. The caller has to examine the future to find out whether the encapsulated operation has failed.
Likewise when you move the same code into a Runnable like
public CompletionStage<Void> leaveGame(GameService gameService)
{
return CompletableFuture.runAsync(() -> System.out.println("BLAH"))
.thenRun(() -> gameService.deregister(playerName)
.exceptionally(t -> {
LOGGER.info("Could not deregister: {}", t.getMessage());
throw new CompletionException(t);
}));
}
The Runnable will not throw an exception. It’s still required to examine the future returned by gameService.deregister(…).exceptionally(…) to find out whether it failed, but now, you are not returning it but just dropping the reference.
To create a future whose completion depends on a future returned by a function evaluation, you need thenCompose:
public CompletionStage<Void> leaveGame(GameService gameService)
{
return CompletableFuture.runAsync(() -> System.out.println("BLAH"))
.thenCompose(voidArg -> gameService.deregister(playerName)
.exceptionally(t -> {
LOGGER.info("Could not deregister: {}", t.getMessage());
throw new CompletionException(t);
}));
}
So now you’re implementing a Function<Void,CompletionStage<Void>> rather than Runnable and the stage returned by the function will be use to complete the future returned by leaveGame.
Related
I have an async function which does a particular task, sample code below,
#Async
public CompletableFuture<Boolean> foo(String value) throws Exception {
// do task
LOGGER.info("Processing completed");
return CompletableFuture.completedFuture(true);
}
Whenever any runtime exception occurs the caller function is not informed and the application runs without any errors/exceptions. This is dangerous because we will be under the false assumption that the processing is successful.
How to avoid this and make the method report error whenever any exception occurs?
To overcome this you need to add an exception try/catch block and set the exception in the completable future instance
#Async
public CompletableFuture<Boolean> foo(String value) {
CompletableFuture completableFuture = new CompletableFuture();
try{
// do task
LOGGER.info("Processing completed");
completableFuture.complete(true);
} catch(Exception e) {
completableFuture.completeExceptionally(e);
}
return completableFuture;
}
On the caller side,
CompletableFuture<Boolean> foo = service.foo(value);
foo.whenComplete((res, ex) -> {
if (ex == null) {
// success flow ...
} else {
// handle exception ...
ex.printStackTrace();
LOGGER.error("ERROR ----> " + ex.getMessage());
}
});
You can handle exceptions on the caller side using exceptionally method from CompletableFuture
CompletableFuture<Boolean> foo = service.foo(value)
.exceptionally(ex-> /* some action */ false);
How can I run whenComplete of the CompletableFuture in the original thread that CompletableFuture was created in?
// main thread
CompletableFuture
.supplyAsync(() -> {
// some logic here
return null;
}, testExecutorService);
.whenComplete(new BiConsumer<Void, Throwable>() {
#Override
public void accept(Void aVoid, Throwable throwable) {
// run this in the "main" thread
}
});
Expanded example for JavaFx:
button.setOnClick((evt) -> {
// the handler of the click event is called by the GUI-Thread
button.setEnabled(false);
CompletableFuture.supplyAsync(() -> {
// some logic here (runs outside of GUI-Thread)
return something;
}, testExecutorService);
.whenComplete((Object result, Throwable ex) -> {
// this part also runs outside the GUI-Thread
if (exception != null) {
// something went wrong, handle the exception
Platform.runLater(() -> {
// ensure we update the GUI only on the GUI-Thread
label.setText(ex.getMessage());
});
} else {
// job finished successfull, lets use the result
Platform.runLater(() -> {
label.setText("Done");
});
}
Platform.runLater(() -> {
button.setEnabled(true); // lets try again if needed
});
});
});
This is not the best code you could write in this situation, but it should bring the point across.
I am trying to compose a chain of steps such that I can avoid a large nested chain of if and else calls by creating methods that return CompletableFuture<Boolean> in a manner such as....
client.connect(identifier).thenCompose(b -> client.authenticate())
.thenCompose(b -> client.sendSetting(settings))
.thenCompose(b -> client.saveSettings())
.thenCompose(b -> client.sendKey(key))
.thenCompose(b -> client.setBypassMode(true))
.thenCompose(b -> client.start())
.whenComplete((success, ex) -> {
if(ex == null) {
System.out.println("Yay");
} else {
System.out.println("Nay");
}
});
If the client methods return a CompletableFuture<Boolean> deciding whether to continue processing has to be done in each lambda in the chain and doesn't provide a method to abort early if one of the calls fail. I would rather have the calls return CompletableFuture<Void> and use Exceptions to control if 1) each successive step in the chain executes and 2) final determination of success of the full chain.
I am having trouble finding which method on CompletableFuture<Void> to swap for thenCompose to make things work (let alone compile).
public class FutureChaings {
public static CompletableFuture<Void> op(boolean fail) {
CompletableFuture<Void> future = new CompletableFuture<Void>();
System.out.println("op");
Executors.newScheduledThreadPool(1).schedule(() -> {
if(fail) {
future.completeExceptionally(new Exception());
}
future.complete(null);
}, 1, TimeUnit.SECONDS);
return future;
}
public static void main(String[] args) {
op(false).thenCompose(b -> op(false)).thenCompose(b -> op(true)).whenComplete((b, ex) -> {
if(ex != null) {
System.out.println("fail");
} else {
System.out.println("success");
}
});
}
}
I was able to contrive an example that behaved the way I wanted. So I know what calls to put together to get what I want. Now to figure out what the compiler doesn't like in my real code. Thanks for the comments.
I am new to CompletableFuture, I will like to call a method MetadataLoginUtil::login which can throw an exception. However, the code below is not compiled although I have 'exceptionally' written. It says that I must wrap the MetadataLoginUtil::login' within try & catch.
Please advise.
Thanks ahead !
public void run() throws ConnectionException {
CompletableFuture<Void> mt = CompletableFuture.supplyAsync(MetadataLoginUtil::login)
.exceptionally(e -> {
System.out.println(e);
return null;
})
.thenAccept(e -> System.out.println(e));
}
This is not a deficiency of how CompletableFuture works in general, but of the convenience methods, all using functional interfaces not allowing checked exceptions. You can solve this with an alternative supplyAsync method:
public static <T> CompletableFuture<T> supplyAsync(Callable<T> c) {
CompletableFuture<T> f=new CompletableFuture<>();
CompletableFuture.runAsync(() -> {
try { f.complete(c.call()); } catch(Throwable t) { f.completeExceptionally(t); }
});
return f;
}
This is basically doing the same as the original supplyAsync, but allowing checked exceptions. So you can use it right like in your original attempt, only redirecting the initial supplyAsync call.
CompletableFuture<Void> mt = supplyAsync(MetadataLoginUtil::login)
.exceptionally(e -> { System.out.println(e); return null; } )
.thenAccept(e -> System.out.println(e));
CompletableFuture.supplyAsync(Supplier<U>) expects a java.util.function.Supplier<U> instance, and Supplier.get() method's signature does not allow for checked exceptions. To see this clearly, notice that CompletableFuture.supplyAsync(MetadataLoginUtil::login) is equivalent to
CompletableFuture<Void> mt = CompletableFuture.supplyAsync(new Supplier<Void>() {
#Override
public Void get() {
return MetadataLoginUtil.login();
}
})
which clearly cannot compile.
You can handle the exception inside your Supplier, changing CompletableFuture.supplyAsync(MetadataLoginUtil::login).exceptionally(e -> {System.out.println(e); return null; } ) to
CompletableFuture.supplyAsync(() -> {
try {
return MetadataLoginUtil.login();
} catch (Exception e) {
System.out.println(e);
return null;
}
})
It's not pretty, but CompletableFuture's API doesn't seem to work with checked exceptions very well.
I made these unit tests, and the outcome is not what I expected at all:
// This one outputs "subscribe.onError"
#Test
public void observable_doOnError_subscribingToError() throws InterruptedException {
Observable<String> obs = getErrorProducingObservable();
obs.doOnError(throwable -> System.out.println("doOnError"));
obs.subscribeOn(Schedulers.immediate()).observeOn(Schedulers.immediate()).subscribe(
s -> {},
error -> System.out.println("subscribe.onError")
);
Thread.sleep(300);
}
// This one outputs "subscribe.onError"
#Test
public void observable_onErrorReturn() throws InterruptedException {
Observable<String> obs = getErrorProducingObservable();
obs.onErrorReturn(throwable -> "Yeah I got this");
obs.subscribeOn(Schedulers.immediate()).observeOn(Schedulers.immediate()).subscribe(
s -> System.out.println("got: " + s),
error -> System.out.println("subscribe.onError")
);
Thread.sleep(300);
}
private Observable<String> getErrorProducingObservable() {
return Observable.create(subscriber -> {
subscriber.onError(new RuntimeException("Somebody set up us the bomb"));
});
}
So both output "subscribe.onError" - neither doOnError nor onErrorReturn seems to be called.
doOnErroris documented as:
Modifies the source Observable so that it invokes an action if it calls onError.
I'm not sure how to intepret that, but I expected either "doOnError" to be output or "doOnError" followed by "subscribe.onError".
onErrorReturn is documented as :
Instructs an Observable to emit an item (returned by a specified function) rather than invoking onError if it encounters an error.
Hence I was expecting "got: Yeah I got this" as output from the latter test.
What gives?
Both doOnError and onErrorReturn returns a new Observable with the changed behaviour. I agree that the documentation of them may be a little misleading. Modify your tests like this to get the expected behaviour:
// This one outputs "subscribe.onError"
#Test
public void observable_doOnError_subscribingToError() throws InterruptedException {
Observable<String> obs =
getErrorProducingObservable()
.doOnError(throwable -> System.out.println("doOnError"));
obs.subscribeOn(Schedulers.immediate()).observeOn(Schedulers.immediate()).subscribe(
s -> {},
error -> System.out.println("subscribe.onError")
);
Thread.sleep(300);
}
// This one outputs "subscribe.onError"
#Test
public void observable_onErrorReturn() throws InterruptedException {
Observable<String> obs =
getErrorProducingObservable()
.onErrorReturn(throwable -> "Yeah I got this");
obs.subscribeOn(Schedulers.immediate()).observeOn(Schedulers.immediate()).subscribe(
s -> System.out.println("got: " + s),
error -> System.out.println("subscribe.onError")
);
Thread.sleep(300);
}
private Observable<String> getErrorProducingObservable() {
return Observable.create(subscriber -> {
subscriber.onError(new RuntimeException("Somebody set up us the bomb"));
});
}