Skip to content

Commit b62bed7

Browse files
committed
iluwatar#590 explanation for Promise
1 parent 54c0b17 commit b62bed7

File tree

1 file changed

+272
-6
lines changed

1 file changed

+272
-6
lines changed

promise/README.md

Lines changed: 272 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,277 @@ tags:
99
---
1010

1111
## Also known as
12+
1213
CompletableFuture
1314

1415
## Intent
15-
A Promise represents a proxy for a value not necessarily known when the promise is created. It
16-
allows you to associate dependent promises to an asynchronous action's eventual success value or
17-
failure reason. Promises are a way to write async code that still appears as though it is executing
18-
in a synchronous way.
16+
17+
A Promise represents a proxy for a value not necessarily known when the promise is created. It allows you to associate
18+
dependent promises to an asynchronous action's eventual success value or failure reason. Promises are a way to write
19+
async code that still appears as though it is executing in a synchronous way.
20+
21+
## Explanation
22+
23+
The Promise object is used for asynchronous computations. A Promise represents an operation that hasn't completed yet,
24+
but is expected in the future.
25+
26+
Promises provide a few advantages over callback objects:
27+
* Functional composition and error handling
28+
* Prevents callback hell and provides callback aggregation
29+
30+
Real world example
31+
32+
> We are developing a software solution that downloads files and calculates the number of lines and character
33+
frequencies in those files. Promise is an ideal solution to make the code concise and easy to understand.
34+
35+
In plain words
36+
37+
> Promise is a placeholder for an asynchronous operation that is ongoing.
38+
39+
Wikipedia says
40+
41+
> In computer science, future, promise, delay, and deferred refer to constructs used for synchronizing program
42+
execution in some concurrent programming languages. They describe an object that acts as a proxy for a result that is
43+
initially unknown, usually because the computation of its value is not yet complete.
44+
45+
**Programmatic Example**
46+
47+
In the example a file is downloaded and its line count is calculated. The calculated line count is then consumed and
48+
printed on console.
49+
50+
Let's first introduce a support class we need for implementation. Here's `PromiseSupport`.
51+
52+
```java
53+
class PromiseSupport<T> implements Future<T> {
54+
55+
private static final Logger LOGGER = LoggerFactory.getLogger(PromiseSupport.class);
56+
57+
private static final int RUNNING = 1;
58+
private static final int FAILED = 2;
59+
private static final int COMPLETED = 3;
60+
61+
private final Object lock;
62+
63+
private volatile int state = RUNNING;
64+
private T value;
65+
private Exception exception;
66+
67+
PromiseSupport() {
68+
this.lock = new Object();
69+
}
70+
71+
void fulfill(T value) {
72+
this.value = value;
73+
this.state = COMPLETED;
74+
synchronized (lock) {
75+
lock.notifyAll();
76+
}
77+
}
78+
79+
void fulfillExceptionally(Exception exception) {
80+
this.exception = exception;
81+
this.state = FAILED;
82+
synchronized (lock) {
83+
lock.notifyAll();
84+
}
85+
}
86+
87+
@Override
88+
public boolean cancel(boolean mayInterruptIfRunning) {
89+
return false;
90+
}
91+
92+
@Override
93+
public boolean isCancelled() {
94+
return false;
95+
}
96+
97+
@Override
98+
public boolean isDone() {
99+
return state > RUNNING;
100+
}
101+
102+
@Override
103+
public T get() throws InterruptedException, ExecutionException {
104+
synchronized (lock) {
105+
while (state == RUNNING) {
106+
lock.wait();
107+
}
108+
}
109+
if (state == COMPLETED) {
110+
return value;
111+
}
112+
throw new ExecutionException(exception);
113+
}
114+
115+
@Override
116+
public T get(long timeout, TimeUnit unit) throws ExecutionException {
117+
synchronized (lock) {
118+
while (state == RUNNING) {
119+
try {
120+
lock.wait(unit.toMillis(timeout));
121+
} catch (InterruptedException e) {
122+
LOGGER.warn("Interrupted!", e);
123+
Thread.currentThread().interrupt();
124+
}
125+
}
126+
}
127+
128+
if (state == COMPLETED) {
129+
return value;
130+
}
131+
throw new ExecutionException(exception);
132+
}
133+
}
134+
```
135+
136+
With `PromiseSupport` in place we can implement the actual `Promise`.
137+
138+
```java
139+
public class Promise<T> extends PromiseSupport<T> {
140+
141+
private Runnable fulfillmentAction;
142+
private Consumer<? super Throwable> exceptionHandler;
143+
144+
public Promise() {
145+
}
146+
147+
@Override
148+
public void fulfill(T value) {
149+
super.fulfill(value);
150+
postFulfillment();
151+
}
152+
153+
@Override
154+
public void fulfillExceptionally(Exception exception) {
155+
super.fulfillExceptionally(exception);
156+
handleException(exception);
157+
postFulfillment();
158+
}
159+
160+
private void handleException(Exception exception) {
161+
if (exceptionHandler == null) {
162+
return;
163+
}
164+
exceptionHandler.accept(exception);
165+
}
166+
167+
private void postFulfillment() {
168+
if (fulfillmentAction == null) {
169+
return;
170+
}
171+
fulfillmentAction.run();
172+
}
173+
174+
public Promise<T> fulfillInAsync(final Callable<T> task, Executor executor) {
175+
executor.execute(() -> {
176+
try {
177+
fulfill(task.call());
178+
} catch (Exception ex) {
179+
fulfillExceptionally(ex);
180+
}
181+
});
182+
return this;
183+
}
184+
185+
public Promise<Void> thenAccept(Consumer<? super T> action) {
186+
var dest = new Promise<Void>();
187+
fulfillmentAction = new ConsumeAction(this, dest, action);
188+
return dest;
189+
}
190+
191+
public Promise<T> onError(Consumer<? super Throwable> exceptionHandler) {
192+
this.exceptionHandler = exceptionHandler;
193+
return this;
194+
}
195+
196+
public <V> Promise<V> thenApply(Function<? super T, V> func) {
197+
Promise<V> dest = new Promise<>();
198+
fulfillmentAction = new TransformAction<V>(this, dest, func);
199+
return dest;
200+
}
201+
202+
private class ConsumeAction implements Runnable {
203+
204+
private final Promise<T> src;
205+
private final Promise<Void> dest;
206+
private final Consumer<? super T> action;
207+
208+
private ConsumeAction(Promise<T> src, Promise<Void> dest, Consumer<? super T> action) {
209+
this.src = src;
210+
this.dest = dest;
211+
this.action = action;
212+
}
213+
214+
@Override
215+
public void run() {
216+
try {
217+
action.accept(src.get());
218+
dest.fulfill(null);
219+
} catch (Throwable throwable) {
220+
dest.fulfillExceptionally((Exception) throwable.getCause());
221+
}
222+
}
223+
}
224+
225+
private class TransformAction<V> implements Runnable {
226+
227+
private final Promise<T> src;
228+
private final Promise<V> dest;
229+
private final Function<? super T, V> func;
230+
231+
private TransformAction(Promise<T> src, Promise<V> dest, Function<? super T, V> func) {
232+
this.src = src;
233+
this.dest = dest;
234+
this.func = func;
235+
}
236+
237+
@Override
238+
public void run() {
239+
try {
240+
dest.fulfill(func.apply(src.get()));
241+
} catch (Throwable throwable) {
242+
dest.fulfillExceptionally((Exception) throwable.getCause());
243+
}
244+
}
245+
}
246+
}
247+
```
248+
249+
Now we can show the full example in action. Here's how to download and count the number of lines in a file using
250+
`Promise`.
251+
252+
```java
253+
countLines().thenAccept(
254+
count -> {
255+
LOGGER.info("Line count is: {}", count);
256+
taskCompleted();
257+
}
258+
);
259+
260+
private Promise<Integer> countLines() {
261+
return download(DEFAULT_URL).thenApply(Utility::countLines);
262+
}
263+
264+
private Promise<String> download(String urlString) {
265+
return new Promise<String>()
266+
.fulfillInAsync(
267+
() -> Utility.downloadFile(urlString), executor)
268+
.onError(
269+
throwable -> {
270+
throwable.printStackTrace();
271+
taskCompleted();
272+
}
273+
);
274+
}
275+
```
19276

20277
## Class diagram
278+
21279
![alt text](./etc/promise.png "Promise")
22280

23281
## Applicability
282+
24283
Promise pattern is applicable in concurrent programming when some work needs to be done asynchronously
25284
and:
26285

@@ -35,10 +294,17 @@ and:
35294
* [Guava ListenableFuture](https://github.com/google/guava/wiki/ListenableFutureExplained)
36295

37296
## Related Patterns
38-
* Async Method Invocation
39-
* Callback
297+
298+
* [Async Method Invocation](https://java-design-patterns.com/patterns/async-method-invocation/)
299+
* [Callback](https://java-design-patterns.com/patterns/callback/)
300+
301+
## Tutorials
302+
303+
* [Guide To CompletableFuture](https://www.baeldung.com/java-completablefuture)
40304

41305
## Credits
42306

43307
* [You are missing the point to Promises](https://gist.github.com/domenic/3889970)
44308
* [Functional style callbacks using CompletableFuture](https://www.infoq.com/articles/Functional-Style-Callbacks-Using-CompletableFuture)
309+
* [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)
310+
* [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)

0 commit comments

Comments
 (0)