Search code examples
javapromise

How to implement a promise in java which changes the output type


I'm trying to implement a simple promise system in java. I'm doing it for special purpose so please don't recommend any libraries.

I have a problem when I try to implement a thenApply() method which takes a Function as parameter, similar to what CompletableFuture has and therefore returns a promise with another type.

The promise interface:

public interface Promise<T> {
    Promise<T> then(Consumer<T> handler);

    <U> Promise<U> thenApply(Function<T, U> handler);
}

My implementation so far:

public class PromiseImpl<T> implements Promise<T> {

    private List<Consumer<T>> resultHandlers = new ArrayList<>();

    public PromiseImpl(CompletableFuture<T> future) {
        future.thenAccept(this::doWork);
    }

    @Override
    public Promise<T> then(Consumer<T> handler) {
        resultHandlers.add(handler);
        return this;
    }

    @Override
    public <U> Promise<U> thenApply(Function<T, U> handler) {
        // How to implement here??? I don't have the result yet
        handler.apply(?);
    }

    private void onResult(T result) {
        for (Consumer<T> handler : resultHandlers) {
            handler.accept(result);
        }
    }

    private Object doWork(T result) {
        onResult(result);
        return null;
    }
}

The problem is that I don't know the result of my initial future in the thenApply() method, so I cannot call my handler. Also, I don't want to call future.get() because this method is blocking.

How could I make this work?


Solution

  • The real problem is in the design of your Promise type. It is holding a set of callbacks, all of which are to be invoked on completion. This is a fundamental problem (limiting generic functionality around the return type of thenApply's function). This can be resolved by changing your Promise implementation to return a new promise whenever a handler is registered, instead of returning this, such that each promise object will have its own handler to invoke.

    In addition to solving this, it's a better design for functional-style programming, as you can make your Promise objects immutable.

    I would change the interface to be:

    interface Promise<T> {
        <U> Promise<U> thenApply(Function<T, U> handler);
        Promise<Void> thenAccept(Consumer<T> consumer);
    }
    

    The "chaining" of callbacks can then be done around the future objects to which chained Promise instances have references. So the implementation can look like:

    class PromiseImpl<T> implements Promise<T> {
    
        private CompletableFuture<T> future;
    
        public PromiseImpl(CompletableFuture<T> future) {
            this.future = future;
        }
    
        @Override
        public <U> Promise<U> thenApply(Function<T, U> function) {
            return new PromiseImpl<>(this.future.thenApply(function));
        }
    
        @Override
        public Promise<Void> thenAccept(Consumer<T> consumer) {
            return new PromiseImpl<>(this.future.thenAccept(consumer));
        }
    
        private void onResult(T result) {
            this.future.complete(result);
        }
    
        private Object doWork(T result) {
            onResult(result);
            return null;
        }
    }
    

    And using that can be as simple as:

    Promise<String> stringPromise = new PromiseImpl<>(new CompletableFuture<String>());
    Promise<Long> longPromise = stringPromise.thenApply(str -> Long.valueOf(str.length()));
    Promise<Void> voidPromise = stringPromise.thenAccept(str -> System.out.println(str));
    

    EDIT:
    Regarding Michael's comment about retrieving the value: that was not added as it wasn't in the original Promise API. But it's easy enough to add:

    T get(); //To the interface
    

    And implemented with:

    public T get() {
        //try-catch 
        return this.future.get();
    }
    

    Note: this is starting to look more and more like a duplication of CompletableFuture, which raises the question of why do this at all. But assuming there will be additional Promise-like methods in this interface, the method would be wrapping the future API.


    If you need to use the same Promise object with a list of call backs, then you have no choice but to parameterize the Promise interface with both Function concrete type parameters:

    public interface Promise<T, U>
    

    And U wouldn't be able to be a method generic parameter on then or thenApply.