如何在Java中实现可更改输出类型的Promise

时间:2018-07-06 10:28:12

标签: java promise

我正在尝试在Java中实现一个简单的Promise系统。我这样做是出于特殊目的,所以请不要推荐任何库。

当我尝试实现将函数作为参数的thenApply()方法时,遇到了一个问题,类似于CompletableFuture的方法,因此返回了另一个类型的promise。

promise接口:

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

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

到目前为止,我的实现方式是

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;
    }
}

问题是我不知道我在thenApply()方法中最初的未来结果,所以我无法调用处理程序。另外,我不想调用future.get(),因为此方法正在阻止。

我该如何做?

3 个答案:

答案 0 :(得分:3)

真正的问题是您的Promise类型的设计。它拥有一组回调,所有回调都将在完成时被调用。这是一个基本问题(将通用功能限制在thenApply函数的返回类型周围)。这可以通过更改您的Promise实现来解决,只要在注册处理程序时就返回new承诺,而不是返回this,这样每个Promise对象将拥有自己的处理程序来调用。

除了解决此问题之外,它还可以用于函数式编程,因为您可以使Promise对象不可变。

我将界面更改为:

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

然后可以围绕链接的Promise实例引用的将来对象进行回调的“链接”。因此实现看起来像这样:

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;
    }
}

使用它可以很简单:

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));

编辑:
关于Michael's comment检索值:未添加,因为它不是原始Promise API中的值。但这很容易添加:

T get(); //To the interface

并通过以下方式实现:

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

注意:这看起来越来越像是CompletableFuture的副本,这引出了为什么要这么做的问题。但是假设此接口中将有其他类似Promise的方法,则该方法将包装将来的API。


如果您需要使用相同的Promise对象和回调列表,那么别无选择,只能使用两个Promise具体类型参数对Function接口进行参数化:< / p>

public interface Promise<T, U>

并且U不能成为thenthenApply上的方法通用参数。

答案 1 :(得分:2)

如果您想保持班级的其余部分不变,并且只实现thenApply方法,则必须创建一个新的CompletableFuture,因为这是当前您必须构造新的{ {1}}:

Promise

如果您可以为@Override public <U> Promise<U> thenApply(Function<T, U> handler) { CompletableFuture<U> downstream = new CompletableFuture<>(); this.then(t -> downstream.complete(handler.apply(t))); return new PromiseImpl<>(downstream); } 添加一个私有的无参数构造函数,则可以避免创建新的PromiseImpl

CompletableFuture

但是,如果要在@Override public <U> Promise<U> thenApply(Function<T, U> handler) { PromiseImpl result = new PromiseImpl(); this.then(t -> result.doWork(handler.apply(t))); return result; } 上实现自己的API,实际上应该使用装饰器模式并将CompletableFuture实例包装为CompletableFuture中的私有变量。

答案 2 :(得分:0)

您可以返回一些扩展PromiseImpl并覆盖onResult的匿名类,以便处理程序接受应用mapper函数的结果。不要忘记调用父onResult,这样就会调用父处理程序。