Skip to content

Commit

Permalink
docs: update promise
Browse files Browse the repository at this point in the history
  • Loading branch information
iluwatar committed May 17, 2024
1 parent 279d227 commit 6e22be3
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 238 deletions.
311 changes: 82 additions & 229 deletions promise/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,309 +3,162 @@ title: Promise
category: Concurrency
language: en
tag:
- Reactive
- Asynchronous
- Decoupling
- Messaging
- Synchronization
- Thread management
---

## Also known as

CompletableFuture
* Deferred
* Future

## 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.
The Promise design pattern is used to handle asynchronous operations by providing a placeholder for a result that is initially unknown but will be resolved in the future.

## 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.
Real-world example

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 an online pizza ordering system, when a customer places an order, the system immediately acknowledges the order and provides a tracking number (the promise). The pizza preparation and delivery process happens asynchronously in the background. The customer can check the status of their order at any time using the tracking number. Once the pizza is prepared and out for delivery, the customer receives a notification (promise resolved) about the delivery status. If there are any issues, such as an unavailable ingredient or delivery delay, the customer is notified about the error (promise rejected).
>
> This analogy illustrates how the Promise design pattern manages asynchronous tasks, decoupling the initial request from the eventual outcome, and handling both results and errors efficiently.
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.
> 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.
The Promise design pattern is a software design pattern that's often used in concurrent programming to handle asynchronous operations. It represents a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers with an asynchronous action's eventual success value or failure reason.

Let's first introduce a support class we need for implementation. Here's `PromiseSupport`.
In the provided code, the Promise design pattern is used to handle various asynchronous operations such as downloading a file, counting lines in a file, and calculating the character frequency in a file.

```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();
}
}
@Slf4j
public class App {

void fulfillExceptionally(Exception exception) {
this.exception = exception;
this.state = FAILED;
synchronized (lock) {
lock.notifyAll();
}
}
private static final String DEFAULT_URL =
"https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/promise/README.md";
private final ExecutorService executor;

@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
private App() {
// Create a thread pool with 2 threads
executor = Executors.newFixedThreadPool(2);
}

@Override
public boolean isCancelled() {
return false;
public static void main(String[] args) {
var app = new App();
app.promiseUsage();
}

@Override
public boolean isDone() {
return state > RUNNING;
private void promiseUsage() {
calculateLineCount();
calculateLowestFrequencyChar();
}

@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();
private void calculateLowestFrequencyChar() {
// Create a promise to calculate the lowest frequency character
lowestFrequencyChar().thenAccept(
charFrequency -> {
LOGGER.info("Char with lowest frequency is: {}", charFrequency);
}
}
}

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<>(this, dest, func);
return dest;
private void calculateLineCount() {
// Create a promise to calculate the line count
countLines().thenAccept(
count -> {
LOGGER.info("Line count is: {}", count);
}
);
}

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 Promise<Character> lowestFrequencyChar() {
// Create a promise to calculate the character frequency and then find the lowest frequency character
return characterFrequency().thenApply(Utility::lowestFrequencyChar);
}

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());
}
}
private Promise<Map<Character, Long>> characterFrequency() {
// Create a promise to download a file and then calculate the character frequency
return download(DEFAULT_URL).thenApply(Utility::characterFrequency);
}
}
```

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() {
// Create a promise to download a file and then count the lines
return download(DEFAULT_URL).thenApply(Utility::countLines);
}

private Promise<String> download(String urlString) {
// Create a promise to download a file
return new Promise<String>()
.fulfillInAsync(
() -> Utility.downloadFile(urlString), executor)
.onError(
throwable -> {
throwable.printStackTrace();
taskCompleted();
LOGGER.error("An error occurred: ", throwable);
}
);
}
}
```

In this code, the `Promise` class is used to create promises for various operations. The `thenApply` method is used to chain promises, meaning that the result of one promise is used as the input for the next promise. The `thenAccept` method is used to handle the result of a promise. The `fulfillInAsync` method is used to fulfill a promise asynchronously, and the `onError` method is used to handle any errors that occur while fulfilling the promise.

## Class diagram

![alt text](./etc/promise.png "Promise")
![Promise](./etc/promise.png "Promise")

## Applicability

Promise pattern is applicable in concurrent programming when some work needs to be done
asynchronously and:
* When you need to perform asynchronous tasks and handle their results or errors at a later point.
* In scenarios where tasks can be executed in parallel and their outcomes need to be handled once they are completed.
* Suitable for improving the readability and maintainability of asynchronous code.

* Code maintainability and readability suffers due to callback hell.
* You need to compose promises and need better error handling for asynchronous tasks.
* You want to use functional style of programming.
## Tutorials

* [Guide To CompletableFuture](https://www.baeldung.com/java-completablefuture)

## Real world examples
## Known Uses

* [java.util.concurrent.CompletableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html)
* Java's CompletableFuture and Future classes.
* JavaScript’s Promise object for managing asynchronous operations.
* Many asynchronous frameworks and libraries such as RxJava and Vert.x.
* [Guava ListenableFuture](https://github.com/google/guava/wiki/ListenableFutureExplained)

## Related Patterns
## Consequences

* [Async Method Invocation](https://java-design-patterns.com/patterns/async-method-invocation/)
* [Callback](https://java-design-patterns.com/patterns/callback/)
Benefits:

## Tutorials
* Improved Readability: Simplifies complex asynchronous code, making it easier to understand and maintain.
* Decoupling: Decouples the code that initiates the asynchronous operation from the code that processes the result.
* Error Handling: Provides a unified way to handle both results and errors from asynchronous operations.

* [Guide To CompletableFuture](https://www.baeldung.com/java-completablefuture)
Trade-offs:

* Complexity: Can add complexity to the codebase if overused or misused.
* Debugging: Asynchronous code can be harder to debug compared to synchronous code due to the non-linear flow of execution.

## Related Patterns

* [Observer](https://java-design-patterns.com/patterns/observer/): Promises can be used in conjunction with the Observer pattern to notify subscribers about the completion of asynchronous operations.
* [Callback](https://java-design-patterns.com/patterns/callback/): Promises often replace callback mechanisms by providing a more structured and readable way to handle asynchronous results.
* [Async Method Invocation](https://java-design-patterns.com/patterns/async-method-invocation/): Promises are often used to handle the results of asynchronous method invocations, allowing for non-blocking execution and result handling.

## 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)
* [Java Concurrency in Practice](https://amzn.to/4aRMruW)
* [Effective Java](https://amzn.to/4cGk2Jz)
* [Java 8 in Action: Lambdas, Streams, and functional-style programming](https://amzn.to/3QCmGXs)

0 comments on commit 6e22be3

Please sign in to comment.