8000 Merge pull request #470 from iluwatar/Promise · armdev/java-design-patterns@678a06e · GitHub
[go: up one dir, main page]

Skip to content

Commit 678a06e

Browse files
authored
Merge pull request iluwatar#470 from iluwatar/Promise
Implements iluwatar#403 Promise pattern
2 parents e425c2e + 59cf100 commit 678a06e

File tree

11 files changed

+1096
-0
lines changed

11 files changed

+1096
-0
lines changed

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@
129129
<module>hexagonal</module>
130130
<module>abstract-document</module>
131131
<module>aggregator-microservices</module>
132+
<module>promise</module>
132133
<module>page-object</module>
133134
</modules>
134135

promise/README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
---
2+
layout: pattern
3+
title: Promise
4+
folder: promise
5+
permalink: /patterns/promise/
6+
categories: Concurrency
7+
tags:
8+
- Java
9+
- Functional
10+
- Reactive
11+
- Difficulty-Intermediate
12+
---
13+
14+
## Also known as
15+
CompletableFuture
16+
17+
## Intent
18+
A Promise represents a proxy for a value not necessarily known when the promise is created. It
19+
allows you to associate dependent promises to an asynchronous action's eventual success value or
20+
failure reason. Promises are a way to write async code that still appears as though it is executing
21+
in a synchronous way.
22+
23+
![alt text](./etc/promise.png "Promise")
24+
25+
## Applicability
26+
Promise pattern is applicable in concurrent programming when some work needs to be done asynchronously
27+
and:
28+
29+
* code maintainablity and readability suffers due to callback hell.
30+
* you need to compose promises and need better error handling for asynchronous tasks.
31+
* you want to use functional style of programming.
32+
33+
34+
## Real world examples
35+
36+
* [java.util.concurrent.CompletableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html)
37+
* [Guava ListenableFuture](https://github.com/google/guava/wiki/ListenableFutureExplained)
38+
39+
## Related Patterns
40+
* Async Method Invocation
41+
* Callback
42+
43+
## Credits
44+
45+
* [You are missing the point to Promises](https://gist.github.com/domenic/3889970)
46+
* [Functional style callbacks using CompleteableFuture](https://www.infoq.com/articles/Functional-Style-Callbacks-Using-CompletableFuture)

promise/etc/promise.png

57.8 KB
Loading

promise/etc/promise.ucls

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<class-diagram version="1.1.10" icons="true" automaticImage="PNG" always-add-relationships="false"
3+
generalizations="true" realizations="true" associations="true" dependencies="false" nesting-relationships="true"
4+
router="FAN">
5+
<class id="1" language="java" name="com.iluwatar.promise.Promise" project="promise"
6+
file="/promise/src/main/java/com/iluwatar/promise/Promise.java" binary="false" corner="BOTTOM_RIGHT">
7+
<position height="-1" width="-1" x="524" y="541"/>
8+
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
9+
sort-features="false" accessors="true" visibility="true">
10+
<attributes public="true" package="true" protected="true" private="false" static="true"/>
11+
<operations public="true" package="false" protected="true" private="false" static="true"/>
12+
</display>
13+
</class>
14+
<interface id="2" language="java" name="java.util.concurrent.Future" project="async-method-invocation"
15+
file="/usr/lib/java/jdk1.8.0_45/jre/lib/rt.jar" binary="true" corner="BOTTOM_RIGHT">
16+
<position height="-1" width="-1" x="527" y="94"/>
17+
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
18+
sort-features="false" accessors="true" visibility="true">
19+
<attributes public="true" package="true" protected="true" private="true" static="true"/>
20+
<operations public="true" package="true" protected="true" private="true" static="true"/>
21+
</display>
22+
</interface>
23+
<class id="3" language="java" name="com.iluwatar.promise.PromiseSupport" project="promise"
24+
file="/promise/src/main/java/com/iluwatar/promise/PromiseSupport.java" binary="false" corner="BOTTOM_RIGHT">
25+
<position height="-1" width="-1" x="524" y="313"/>
26+
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
27+
sort-features="false" accessors="true" visibility="true">
28+
<attributes public="true" package="false" protected="true" private="false" static="true"/>
29+
<operations public="true" package="false" protected="true" private="true" static="true"/>
30+
</display>
31+
</class>
32+
<interface id="4" language="java" name="java.util.concurrent.Executor" project="async-method-invocation"
33+
file="/usr/lib/java/jdk1.8.0_45/jre/lib/rt.jar" binary="true" corner="BOTTOM_RIGHT">
34+
<position height="-1" width="-1" x="798" y="541"/>
35+
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
36+
sort-features="false" accessors="true" visibility="true">
37+
<attributes public="true" package="true" protected="true" private="true" static="true"/>
38+
<operations public="true" package="true" protected="true" private="true" static="true"/>
39+
</display>
40+
</interface>
41+
<interface id="5" language="java" name="java.util.concurrent.Callable" project="async-method-invocation"
42+
file="/usr/lib/java/jdk1.8.0_45/jre/lib/rt.jar" binary="true" corner="BOTTOM_RIGHT">
43+
<position height="-1" width="-1" x="847" y="345"/>
44+
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
45+
sort-features="false" accessors="true" visibility="true">
46+
<attributes public="true" package="true" protected="true" private="true" static="true"/>
47+
<operations public="true" package="true" protected="true" private="true" static="true"/>
48+
</display>
49+
</interface>
50+
<interface id="6" language="java" name="java.util.function.Consumer" project="async-method-invocation"
51+
file="/usr/lib/java/jdk1.8.0_45/jre/lib/rt.jar" binary="true" corner="BOTTOM_RIGHT">
52+
<position height="-1" width="-1" x="158" y="336"/>
53+
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
54+
sort-features="false" accessors="true" visibility="true">
55+
<attributes public="true" package="true" protected="true" private="true" static="true"/>
56+
<operations public="true" package="true" protected="true" private="false" static="true"/>
57+
</display>
58+
</interface>
59+
<interface id="7" language="java" name="java.util.function.Function" project="async-method-invocation"
60+
file="/usr/lib/java/jdk1.8.0_45/jre/lib/rt.jar" binary="true" corner="BOTTOM_RIGHT">
61+
<position height="-1" width="-1" x="166" y="546"/>
62+
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
63+
sort-features="false" accessors="true" visibility="true">
64+
<attributes public="true" package="true" protected="true" private="true" static="true"/>
65+
<operations public="true" package="true" protected="true" private="false" static="false"/>
66+
</display>
67+
</interface>
68+
<class id="8" language="java" name="com.iluwatar.promise.App" project="promise"
69+
file="/promise/src/main/java/com/iluwatar/promise/App.java" binary="false" corner="BOTTOM_RIGHT">
70+
<position height="-1" width="-1" x="801" y="189"/>
71+
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
72+
sort-features="false" accessors="true" visibility="true">
73+
<attributes public="true" package="true" protected="true" private="false" static="true"/>
74+
<operations public="true" package="true" protected="true" private="false" static="true"/>
75+
</display>
76+
</class>
77+
<dependency id="9">
78+
<end type="SOURCE" refId="1"/>
79+
<end type="TARGET" refId="5"/>
80+
</dependency>
81+
<dependency id="10">
82+
<end type="SOURCE" refId="8"/>
83+
<end type="TARGET" refId="1"/>
84+
</dependency>
85+
<dependency id="11">
86+
<end type="SOURCE" refId="1"/>
87+
<end type="TARGET" refId="6"/>
88+
</dependency>
89+
<generalization id="12">
90+
<end type="SOURCE" refId="1"/>
91+
<end type="TARGET" refId="3"/>
92+
</generalization>
93+
<dependency id="13">
94+
<end type="SOURCE" refId="1"/>
95+
<end type="TARGET" refId="4"/>
96+
</dependency>
97+
<dependency id="14">
98+
<end type="SOURCE" refId="1"/>
99+
<end type="TARGET" refId="7"/>
100+
</dependency>
101+
<realization id="15">
102+
<end type="SOURCE" refId="3"/>
103+
<end type="TARGET" refId="2"/>
104+
</realization>
105+
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
106+
sort-features="false" accessors="true" visibility="true">
107+
<attributes public="true" package="true" protected="true" private="true" static="true"/>
108+
<operations public="true" package="true" protected="true" private="true" static="true"/>
109+
</classifier-display>
110+
<association-display labels="true" multiplicity="true"/>
111+
</class-diagram>

promise/pom.xml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?xml version="1.0"?>
2+
<!--
3+
4+
The MIT License
5+
Copyright (c) 2014 Ilkka Seppälä
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy
8+
of this software and associated documentation files (the "Software"), to deal
9+
in the Software without restriction, including without limitation the rights
10+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
copies of the Software, and to permit persons to whom the Software is
12+
furnished to do so, subject to the following conditions:
13+
14+
The above copyright notice and this permission notice shall be included in
15+
all copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
THE SOFTWARE.
24+
25+
-->
26+
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
27+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
28+
<modelVersion>4.0.0</modelVersion>
29+
<parent>
30+
<groupId>com.iluwatar</groupId>
31+
<artifactId>java-design-patterns</artifactId>
32+
<version>1.13.0-SNAPSHOT</version>
33+
</parent>
34+
<artifactId>promise</artifactId>
35+
<dependencies>
36+
<dependency>
37+
<groupId>junit</groupId>
38+
<artifactId>junit</artifactId>
39+
<scope>test</scope>
40+
</dependency>
41+
<dependency>
42+
<groupId>org.mockito</groupId>
43+
<artifactId>mockito-core</artifactId>
44+
<scope>test</scope>
45+
</dependency>
46+
</dependencies>
47+
</project>
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/**
2+
* The MIT License
3+
* Copyright (c) 2014 Ilkka Seppälä
4+
*
5+
* Permission is hereby granted, free of charge, to any person obtaining a copy
6+
* of this software and associated documentation files (the "Software"), to deal
7+
* in the Software without restriction, including without limitation the rights
8+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
* copies of the Software, and to permit persons to whom the Software is
10+
* furnished to do so, subject to the following conditions:
11+
*
12+
* The above copyright notice and this permission notice shall be included in
13+
* all copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
* THE SOFTWARE.
22+
*/
23+
package com.iluwatar.promise;
24+
import java.util.Map;
25+
import java.util.concurrent.CompletableFuture;
26+
import java.util.concurrent.CountDownLatch;
27+
import java.util.concurrent.ExecutionException;
28+
import java.util.concurrent.ExecutorService;
29+
import java.util.concurrent.Executors;
30+
31+
/**
32+
*
33+
* The Promise object is used for asynchronous computations. A Promise represents an operation
34+
* that hasn't completed yet, but is expected in the future.
35+
*
36+
* <p>A Promise represents a proxy for a value not necessarily known when the promise is created. It
37+
* allows you to associate dependent promises to an asynchronous action's eventual success value or
38+
* failure reason. This lets asynchronous methods return values like synchronous methods: instead
39+
* of the final value, the asynchronous method returns a promise of having a value at some point
40+
* in the future.
41+
*
42+
* <p>Promises provide a few advantages over callback objects:
43+
* <ul>
44+
* <li> Functional composition and error handling
45+
* <li> Prevents callback hell and provides callback aggregation
46+
* </ul>
47+
*
48+
* <p>
49+
* In this application the usage of promise is demonstrated with two examples:
50+
* <ul>
51+
* <li>Count Lines: In this example a file is downloaded and its line count is calculated.
52+
* The calculated line count is then consumed and printed on console.
53+
* <li>Lowest Character Frequency: In this example a file is downloaded and its lowest frequency
54+
* character is found and printed on console. This happens via a chain of promises, we start with
55+
* a file download promise, then a promise of character frequency, then a promise of lowest frequency
56+
* character which is finally consumed and result is printed on console.
57+
* </ul>
58+
*
59+
* @see CompletableFuture
60+
*/
61+
public class App {
62+
63+
private static final String DEFAULT_URL = "https://raw.githubusercontent.com/iluwatar/java-design-patterns/Promise/promise/README.md";
64+
private final ExecutorService executor;
65+
private final CountDownLatch stopLatch;
66+
67+
private App() {
68+
executor = Executors.newFixedThreadPool(2);
69+
stopLatch = new CountDownLatch(2);
70+
}
71+
72+
/**
73+
* Program entry point
74+
* @param args arguments
75+
* @throws InterruptedException if main thread is interrupted.
76+
* @throws ExecutionException if an execution error occurs.
77+
*/
78+
public static void main(String[] args) throws InterruptedException, ExecutionException {
79+
App app = new App();
80+
try {
81+
app.promiseUsage();
82+
} finally {
83+
app.stop();
84+
}
85+
}
86+
87+
private void promiseUsage() {
88+
calculateLineCount();
89+
90+
calculateLowestFrequencyChar();
91+
}
92+
93+
/*
94+
* Calculate the lowest frequency character and when that promise is fulfilled,
95+
* consume the result in a Consumer<Character>
96+
*/
97+
private void calculateLowestFrequencyChar() {
98+
lowestFrequencyChar()
99+
.thenAccept(
100+
charFrequency -> {
101+
System.out.println("Char with lowest frequency is: " + charFrequency);
102+
taskCompleted();
103+
}
104+
);
105+
}
106+
107+
/*
108+
* Calculate the line count and when that promise is fulfilled, consume the result
109+
* in a Consumer<Integer>
110+
*/
111+
private void calculateLineCount() {
112+
countLines()
113+
.thenAccept(
114+
count -> {
115+
System.out.println("Line count is: " + count);
116+
taskCompleted();
117+
}
118+
);
119+
}
120+
121+
/*
122+
* Calculate the character frequency of a file and when that promise is fulfilled,
123+
* then promise to apply function to calculate lowest character frequency.
124+
*/
125+
private Promise<Character> lowestFrequencyChar() {
126+
return characterFrequency()
127+
.thenApply(Utility::lowestFrequencyChar);
128+
}
129+
130+
/*
131+
* Download the file at DEFAULT_URL and when that promise is fulfilled,
132+
* then promise to apply function to calculate character frequency.
133+
*/
134+
private Promise<Map<Character, Integer>> characterFrequency() {
135+
return download(DEFAULT_URL)
136+
.thenApply(Utility::characterFrequency);
137+
}
138+
139+
/*
140+
* Download the file at DEFAULT_URL and when that promise is fulfilled,
141+
* then promise to apply function to count lines in that file.
142+
*/
143+
private Promise<Integer> countLines() {
144+
return download(DEFAULT_URL)
145+
.thenApply(Utility::countLines);
146+
}
147+
148+
/*
149+
* Return a promise to provide the local absolute path of the file downloaded in background.
150+
* This is an async method and does not wait until the file is downloaded.
151+
*/
152+
private Promise<String> download(String urlString) {
153+
Promise<String> downloadPromise = new Promise<String>()
154+
.fulfillInAsync(
155+
() -> {
156+
return Utility.downloadFile(urlString);
157+
}, executor)
158+
.onError(
159+
throwable -> {
160+
throwable.printStackTrace();
161+
taskCompleted();
162+
}
163+
);
164+
165+
return downloadPromise;
166+
}
167+
168+
private void stop() throws InterruptedException {
169+
stopLatch.await();
170+
executor.shutdownNow();
171+
}
172+
173+
private void taskCompleted() {
174+
stopLatch.countDown();
175+
}
176+
}

0 commit comments

Comments
 (0)
0