#590 explanation for Promise
This commit is contained in:
parent
54c0b1725c
commit
b62bed7e43
@ -9,18 +9,277 @@ tags:
|
||||
---
|
||||
|
||||
## Also known as
|
||||
|
||||
CompletableFuture
|
||||
|
||||
## Intent
|
||||
A Promise represents a proxy for a value not necessarily known when the promise is created. It
|
||||
allows you to associate dependent promises to an asynchronous action's eventual success value or
|
||||
failure reason. Promises are a way to write async code that still appears as though it is executing
|
||||
in a synchronous way.
|
||||
|
||||
A Promise represents a proxy for a value not necessarily known when the promise is created. It allows you to associate
|
||||
dependent promises to an asynchronous action's eventual success value or failure reason. Promises are a way to write
|
||||
async code that still appears as though it is executing in a synchronous way.
|
||||
|
||||
## Explanation
|
||||
|
||||
The Promise object is used for asynchronous computations. A Promise represents an operation that hasn't completed yet,
|
||||
but is expected in the future.
|
||||
|
||||
Promises provide a few advantages over callback objects:
|
||||
* Functional composition and error handling
|
||||
* Prevents callback hell and provides callback aggregation
|
||||
|
||||
Real world example
|
||||
|
||||
> We are developing a software solution that downloads files and calculates the number of lines and character
|
||||
frequencies in those files. Promise is an ideal solution to make the code concise and easy to understand.
|
||||
|
||||
In plain words
|
||||
|
||||
> Promise is a placeholder for an asynchronous operation that is ongoing.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> In computer science, future, promise, delay, and deferred refer to constructs used for synchronizing program
|
||||
execution in some concurrent programming languages. They describe an object that acts as a proxy for a result that is
|
||||
initially unknown, usually because the computation of its value is not yet complete.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
In the example a file is downloaded and its line count is calculated. The calculated line count is then consumed and
|
||||
printed on console.
|
||||
|
||||
Let's first introduce a support class we need for implementation. Here's `PromiseSupport`.
|
||||
|
||||
```java
|
||||
class PromiseSupport<T> implements Future<T> {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(PromiseSupport.class);
|
||||
|
||||
private static final int RUNNING = 1;
|
||||
private static final int FAILED = 2;
|
||||
private static final int COMPLETED = 3;
|
||||
|
||||
private final Object lock;
|
||||
|
||||
private volatile int state = RUNNING;
|
||||
private T value;
|
||||
private Exception exception;
|
||||
|
||||
PromiseSupport() {
|
||||
this.lock = new Object();
|
||||
}
|
||||
|
||||
void fulfill(T value) {
|
||||
this.value = value;
|
||||
this.state = COMPLETED;
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
void fulfillExceptionally(Exception exception) {
|
||||
this.exception = exception;
|
||||
this.state = FAILED;
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return state > RUNNING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get() throws InterruptedException, ExecutionException {
|
||||
synchronized (lock) {
|
||||
while (state == RUNNING) {
|
||||
lock.wait();
|
||||
}
|
||||
}
|
||||
if (state == COMPLETED) {
|
||||
return value;
|
||||
}
|
||||
throw new ExecutionException(exception);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(long timeout, TimeUnit unit) throws ExecutionException {
|
||||
synchronized (lock) {
|
||||
while (state == RUNNING) {
|
||||
try {
|
||||
lock.wait(unit.toMillis(timeout));
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.warn("Interrupted!", e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state == COMPLETED) {
|
||||
return value;
|
||||
}
|
||||
throw new ExecutionException(exception);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
With `PromiseSupport` in place we can implement the actual `Promise`.
|
||||
|
||||
```java
|
||||
public class Promise<T> extends PromiseSupport<T> {
|
||||
|
||||
private Runnable fulfillmentAction;
|
||||
private Consumer<? super Throwable> exceptionHandler;
|
||||
|
||||
public Promise() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fulfill(T value) {
|
||||
super.fulfill(value);
|
||||
postFulfillment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fulfillExceptionally(Exception exception) {
|
||||
super.fulfillExceptionally(exception);
|
||||
handleException(exception);
|
||||
postFulfillment();
|
||||
}
|
||||
|
||||
private void handleException(Exception exception) {
|
||||
if (exceptionHandler == null) {
|
||||
return;
|
||||
}
|
||||
exceptionHandler.accept(exception);
|
||||
}
|
||||
|
||||
private void postFulfillment() {
|
||||
if (fulfillmentAction == null) {
|
||||
return;
|
||||
}
|
||||
fulfillmentAction.run();
|
||||
}
|
||||
|
||||
public Promise<T> fulfillInAsync(final Callable<T> task, Executor executor) {
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
fulfill(task.call());
|
||||
} catch (Exception ex) {
|
||||
fulfillExceptionally(ex);
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
public Promise<Void> thenAccept(Consumer<? super T> action) {
|
||||
var dest = new Promise<Void>();
|
||||
fulfillmentAction = new ConsumeAction(this, dest, action);
|
||||
return dest;
|
||||
}
|
||||
|
||||
public Promise<T> onError(Consumer<? super Throwable> exceptionHandler) {
|
||||
this.exceptionHandler = exceptionHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public <V> Promise<V> thenApply(Function<? super T, V> func) {
|
||||
Promise<V> dest = new Promise<>();
|
||||
fulfillmentAction = new TransformAction<V>(this, dest, func);
|
||||
return dest;
|
||||
}
|
||||
|
||||
private class ConsumeAction implements Runnable {
|
||||
|
||||
private final Promise<T> src;
|
||||
private final Promise<Void> dest;
|
||||
private final Consumer<? super T> action;
|
||||
|
||||
private ConsumeAction(Promise<T> src, Promise<Void> dest, Consumer<? super T> action) {
|
||||
this.src = src;
|
||||
this.dest = dest;
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
action.accept(src.get());
|
||||
dest.fulfill(null);
|
||||
} catch (Throwable throwable) {
|
||||
dest.fulfillExceptionally((Exception) throwable.getCause());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TransformAction<V> implements Runnable {
|
||||
|
||||
private final Promise<T> src;
|
||||
private final Promise<V> dest;
|
||||
private final Function<? super T, V> func;
|
||||
|
||||
private TransformAction(Promise<T> src, Promise<V> dest, Function<? super T, V> func) {
|
||||
this.src = src;
|
||||
this.dest = dest;
|
||||
this.func = func;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
dest.fulfill(func.apply(src.get()));
|
||||
} catch (Throwable throwable) {
|
||||
dest.fulfillExceptionally((Exception) throwable.getCause());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now we can show the full example in action. Here's how to download and count the number of lines in a file using
|
||||
`Promise`.
|
||||
|
||||
```java
|
||||
countLines().thenAccept(
|
||||
count -> {
|
||||
LOGGER.info("Line count is: {}", count);
|
||||
taskCompleted();
|
||||
}
|
||||
);
|
||||
|
||||
private Promise<Integer> countLines() {
|
||||
return download(DEFAULT_URL).thenApply(Utility::countLines);
|
||||
}
|
||||
|
||||
private Promise<String> download(String urlString) {
|
||||
return new Promise<String>()
|
||||
.fulfillInAsync(
|
||||
() -> Utility.downloadFile(urlString), executor)
|
||||
.onError(
|
||||
throwable -> {
|
||||
throwable.printStackTrace();
|
||||
taskCompleted();
|
||||
}
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
Promise pattern is applicable in concurrent programming when some work needs to be done asynchronously
|
||||
and:
|
||||
|
||||
@ -35,10 +294,17 @@ and:
|
||||
* [Guava ListenableFuture](https://github.com/google/guava/wiki/ListenableFutureExplained)
|
||||
|
||||
## Related Patterns
|
||||
* Async Method Invocation
|
||||
* Callback
|
||||
|
||||
* [Async Method Invocation](https://java-design-patterns.com/patterns/async-method-invocation/)
|
||||
* [Callback](https://java-design-patterns.com/patterns/callback/)
|
||||
|
||||
## Tutorials
|
||||
|
||||
* [Guide To CompletableFuture](https://www.baeldung.com/java-completablefuture)
|
||||
|
||||
## Credits
|
||||
|
||||
* [You are missing the point to Promises](https://gist.github.com/domenic/3889970)
|
||||
* [Functional style callbacks using CompletableFuture](https://www.infoq.com/articles/Functional-Style-Callbacks-Using-CompletableFuture)
|
||||
* [Java 8 in Action: Lambdas, Streams, and functional-style programming](https://www.amazon.com/gp/product/1617291994/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617291994&linkId=995af46887bb7b65e6c788a23eaf7146)
|
||||
* [Modern Java in Action: Lambdas, streams, functional and reactive programming](https://www.amazon.com/gp/product/1617293563/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617293563&linkId=f70fe0d3e1efaff89554a6479c53759c)
|
||||
|
Loading…
x
Reference in New Issue
Block a user