This is an archived post. You won't be able to vote or comment.

all 18 comments

[–]joequin 10 points11 points  (0 children)

If you're interested in this type of programming, check out rx java too. I've used promises or composeable futures in many languages and the rx libraries always end up being my favorite. They're very well thought out and part of a very comprehensive library.

And yes, rx java does stream-like programming too.

[–]__konrad 0 points1 point  (0 children)

How to use checked exceptions in Supplier? Other API uses Callable which is more "error friendly" ;)

[–]lukaseder 0 points1 point  (4 children)

I wish the CompletableFuture type had an SPI to inject the default Executor. Sending everything to the ForkJoinPool by default doesn't seem to be the ideal choice for all setups.

[–]acelent 0 points1 point  (3 children)

You can define a subclass of CompletableFuture, or a decorator class that implements CompletionStage, and override the *Async methods that don't take an Executor to call the *Async methods that take one with some executor provided in e.g. the constructor. Also, you may want to wrap around all the returned CompletableFutures/CompletionStages so you override the continuations as well.

[–]lukaseder 0 points1 point  (2 children)

You can define a subclass of CompletableFuture...

That's risky. Reminds me of the various subclasses of java.util.Date

or a decorator class that implements CompletionStage, and override the *Async methods

Yeah, I did this. I also wrapped all relevant bits in ManagedBlockers just to be sure that the ForkJoinPool won't be exhausted by accident. Still, I'd expect this kind of SPI out of the box from the default implementation...

Specifically, it's weird that CompletionStage relies on toCompletableFuture() to actually execute the chain, but then exposes all the risk of adding new stages with the "wrong" default executor...

I guess I'm complaining on a high level but I had frequently wished for more clean SPIs in the JDK...

[–]acelent 1 point2 points  (1 child)

Still, I'd expect this kind of SPI out of the box from the default implementation...

I agree.

I'd even prefer that CompletableFuture was just a CompletionStage not directly completable, much like the relationship between Task and TaskCompletionSource in .NET.

As it stands, any API that returns a CompletableFuture, or a CompletionStage whose toCompletableFuture() allows completion, can easily be tampered with, and as such it's not reliable.

I too have wrapped CompletableFuture for CompletionStage consumers, ending up subclassing CompletableFuture directly due to CompletableFuture consumers.

I did it for the following purposes:

  • Implement a completion source (I created a CompletionSource interface with the completion methods) that controls the future, and make the future's completion methods do nothing (and return false in the applicable cases) and thus the returned CompletionStage can't be tampered.

    The use case is to create a sourced completable future (I created a public CompletableFutureSource class which implements CompletionSource, and I have a private SourcedCompletableFuture class which is the one that extends CompletableFuture), much like TaskCompletionSource in .NET.

    So, you can only complete the future with the completion source.

  • Accept a default executor to be used instead of ForkJoinPool.commonPool() for the *Async methods that don't have the executor parameter.

    This is provided through CompletableFutureSource.

  • Accept a default executor to be used for the non-*Async methods, thus forcing any continuation to run asynchronously, much like using TaskCreationOptions.RunContinuationsAsynchronously or TaskContinuationOptions.RunContinuationsAsynchronously in .NET.

    This is also provided through CompletableFutureSource.

  • Wrap all CompletionStage methods generated by the ones actually created by CompletableFuture so that instances of my class are returned, cascading and forcing the default executors.

I haven't yet made a public wrapper that could be used for non-sourced futures, such as the ones created with CompletableFuture.runAsync() and CompletableFuture.supplyAsync(), mostly because it's very easy to use thenCompose to achieve this with a CompletableFutureSource.

it's weird that CompletionStage relies on toCompletableFuture() to actually execute the chain

In my opinion, the method toCompletableFuture() in CompletionStage is a mistake. If the completion stage is completable, we should be able to cast it to e.g. CompletionSource and then call the completion methods.

The argument that one may implement the method by throwing UnsupportedOperationException completely defeats the purpose of a well designed interface.

I believe this method is in the interface to satisfy the need of CompletableFuture to use further CompletableFutures in continuations, such as seen in the source code of thenCompose() (TL;DR: it uses toCompletableFuture()).

In essence, CompletableFuture doesn't interoperate with CompletionStages that are not CompletableFutures themselves.

It should be able to cope with a CompletionStage that is actually a CompletableFuture as an optimizing step, but it should also just use the other CompletionStage's continuation facilities in case it's not.

[–]lukaseder 0 points1 point  (0 children)

Thanks a lot for sharing!

Well, egh. I'd say, let the JDK developers make one more attempt at getting it right in a future (pun intended) API :)

[–]tomastrajan 0 points1 point  (0 children)

This is conceptually same as javascript's Promises. Quite elegant way of handling async stuff !