├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src ├── main └── java │ └── org │ └── riversun │ └── promise │ ├── Action.java │ ├── Func.java │ ├── Log.java │ ├── Promise.java │ ├── PromiseException.java │ ├── Status.java │ ├── SyncPromise.java │ └── Thennable.java └── test └── java └── org └── riversun └── promise ├── AppTest.java ├── TestPromiseAllAsync.java ├── TestPromiseAllSync.java ├── TestPromiseAllTestCase.java ├── TestPromiseAsync.java ├── TestPromiseSync.java └── TestPromiseTestCase.java /.gitignore: -------------------------------------------------------------------------------- 1 | # for Eclipse 2 | .classpath 3 | .project 4 | .settings 5 | 6 | # for IDEA 7 | *.iml 8 | *.ipr 9 | *.iws 10 | .idea/ 11 | 12 | # for build 13 | target 14 | build 15 | 16 | # for mac 17 | .DS_Store 18 | 19 | /bin/ 20 | /examples/ 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2019 Tom Misawa 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | **java-promise** is a Promise Library for Java. 3 | 4 | - You can easily control asynchronous operations like JavaScript's **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)**. 5 | - Supports both synchronous and asynchronous execution. 6 | 7 | It is licensed under [MIT](https://opensource.org/licenses/MIT). 8 | 9 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.riversun/java-promise/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.riversun/java-promise) 10 | 11 | 12 | # Quick Look 13 | 14 | **Writing a Promise in Javascript** 15 | 16 | A typical example of using promise in JavaScript is: 17 | 18 | ```JavaScript 19 | Promise.resolve('foo') 20 | .then(function (data) { 21 | return new Promise(function (resolve, reject) { 22 | setTimeout(function () { 23 | const newData = data + 'bar'; 24 | resolve(newData); 25 | }, 1); 26 | }); 27 | }) 28 | .then(function (data) { 29 | return new Promise(function (resolve, reject) { 30 | console.log(data); 31 | }); 32 | }); 33 | console.log("Promise in JavaScript"); 34 | ``` 35 | 36 | Result: 37 | 38 | ``` 39 | Promise in JavaScript 40 | foobar 41 | ``` 42 | 43 | **Writing a Promise in java-promise** 44 | 45 | Write the same thing using **java-promise** 46 | 47 | ```Java 48 | import org.riversun.promise.Promise; 49 | public class Example { 50 | 51 | public static void main(String[] args) { 52 | Promise.resolve("foo") 53 | .then(new Promise((action, data) -> { 54 | new Thread(() -> { 55 | String newData = data + "bar"; 56 | action.resolve(newData); 57 | }).start(); 58 | })) 59 | .then(new Promise((action, data) -> { 60 | System.out.println(data); 61 | action.resolve(); 62 | })) 63 | .start(); 64 | System.out.println("Promise in Java"); 65 | } 66 | } 67 | ``` 68 | 69 | Result: 70 | 71 | ``` 72 | Promise in Java 73 | foobar 74 | ``` 75 | 76 | **Syntax:** 77 | Yes,you can write in a syntax similar to JavaScript as follows: 78 | 79 | ```Java 80 | Promise.resolve() 81 | .then(new Promise(funcFulfilled1), new Promise(funcRejected1)) 82 | .then(new Promise(functionFulfilled2), new Promise(functionRejected2)) 83 | .start(); 84 | ``` 85 | 86 | 87 | 88 | # Dependency 89 | 90 | **Maven** 91 | 92 | ```xml 93 | 94 | org.riversun 95 | java-promise 96 | 1.1.0 97 | 98 | ``` 99 | 100 | **Gradle** 101 | 102 | ``` 103 | compile group: 'org.riversun', name: 'java-promise', version: '1.1.0' 104 | ``` 105 | 106 | # Quick Start 107 | 108 | ### Execute sequentially by chained "then" 109 | 110 | - Use **Promise.then()** to chain operations. 111 | - Write your logic in **Func.run(action,data)**. 112 | - Start operation by **Promise.start** and run **asynchronously**(run on worker thread) 113 | - Calling **action.resolve** makes the promise **fullfilled** state and passes the **result** to the next then 114 | 115 | ```java 116 | public class Example00 { 117 | 118 | public static void main(String[] args) { 119 | 120 | Func function1 = (action, data) -> { 121 | new Thread(() -> { 122 | System.out.println("Process-1"); 123 | try { 124 | Thread.sleep(500); 125 | } catch (InterruptedException e) {} 126 | //Specify result value.(Any type can be specified) 127 | action.resolve("Result-1"); 128 | }).start(); 129 | }; 130 | 131 | Func function2 = (action, data) -> { 132 | System.out.println("Process-2 result=" + data); 133 | action.resolve(); 134 | }; 135 | 136 | Promise.resolve() 137 | .then(new Promise(function1)) 138 | .then(new Promise(function2)) 139 | .start();// start Promise operation 140 | 141 | System.out.println("Hello,Promise"); 142 | } 143 | } 144 | ``` 145 | 146 | **Diagram:** 147 | 148 | 149 | **Result:** 150 | 151 | ``` 152 | Hello,Promise 153 | Process-1 154 | Process-1 result=Result-1 155 | ``` 156 | 157 | **Tips** 158 | 159 | It's also OK to just write ``Promise.then(func) ``. 160 | 161 | ```Java 162 | Promise.resolve() 163 | .then(function1) 164 | .then(function2) 165 | .start();// start Promise operation 166 | ``` 167 | 168 | # Description 169 | ### What is "**Func**" ? 170 | 171 | **Func** is a java interface equivalent to JavaScript's **Function** for argument of **#then** 172 | 173 | ```java 174 | public interface Func { 175 | public void run(Action action, Object data) throws Exception; 176 | } 177 | ``` 178 | 179 | You can write **Func** like a JavaScript function. 180 | I want to show two ways of implementing **Func** class. 181 | 182 | No.1)Write **Func** object in the normal way. 183 | 184 | ```Java 185 | Func function = new Func() { 186 | @Override 187 | public void run(Action action, Object data) throws Exception { 188 | System.out.println("Process");//write your logic 189 | action.resolve(); 190 | } 191 | }; 192 | ``` 193 | 194 | No.2)Write **Func** object using lambda expression. 195 | 196 | ```Java 197 | Func function = (action, data) -> { 198 | System.out.println("Process");//write your logic 199 | action.resolve(); 200 | }; 201 | ``` 202 | 203 | ### What is "**Action**" ? 204 | 205 | Action object is an argument of **Func#run** method. 206 | 207 | - Call **action.resolve( [fulfillment value] )** to make the Promise's status **fulfilled** and move on to the next processing(specified by then) with the result(**fulfillment value**). 208 | 209 | ```Java 210 | action.resolve("Success"); 211 | ``` 212 | 213 | - Call **action.reject( [rejection reason] )** to make the Promise's status **rejected** and move on to the next processing(specified by then) with the result(**rejection reason**). 214 | 215 | ```Java 216 | action.reject("Failure"); 217 | ``` 218 | 219 | - Argument is optional, you can call **action.resolve()** or **action.reject()** 220 | 221 | ```Java 222 | action.resolve();//Argument can be omitted 223 | ``` 224 | 225 | # Usage 226 | 227 | ### Rejection 228 | 229 | If **action.reject()** is called, or if an **exception thrown** while executing **Func.run()**, **rejected** status is set to Promise, and the **onRejected** function specified to **then** is called. 230 | 231 | - call ``action.reject`` 232 | 233 | ```Java 234 | Func function = (action, data) -> { 235 | action.reject("Failure"); 236 | }; 237 | ``` 238 | 239 | - throw an exception 240 | 241 | ```Java 242 | Func function = (action, data) -> { 243 | throw new Exception("something"); 244 | }; 245 | 246 | ``` 247 | 248 | Let's see **Promise.then()** method, 249 | the **2nd argument** of **Promise.then()** can be set to a **Func** to receive the result of **rejection** when receiving the result of **then**. 250 | 251 | - **Syntax** 252 | Usage ``Promise.then(onFulfilled[, onRejected]);`` 253 | 254 | - **onFulfilled** is a **Func** object called if the Promise is fulfilled. 255 | You can receive the previous execution **"fulfilled"** result as an argument named **data**. 256 | 257 | - **onRejected** is a **Func** object called if the Promise is rejected. 258 | You can receive the previous execution **"rejected"** result(mainly the objects are exceptions) as an argument named **data**. 259 | 260 | ```Java 261 | //Rejection 262 | public class ExampleRejection { 263 | public static void main(String[] args) { 264 | Promise.resolve() 265 | .then((action, data) -> { 266 | System.out.println("Process-1"); 267 | action.reject(); 268 | }) 269 | .then( 270 | // call when resolved 271 | (action, data) -> { 272 | System.out.println("Resolved Process-2"); 273 | action.resolve(); 274 | }, 275 | // call when rejected 276 | (action, data) -> { 277 | System.out.println("Rejected Process-2"); 278 | action.resolve(); 279 | }) 280 | .start();// start Promise operation 281 | 282 | System.out.println("Hello,Promise"); 283 | } 284 | } 285 | ``` 286 | 287 | **Diagram:** 288 | 289 | 290 | 291 | **Result:** 292 | 293 | ``` 294 | Hello,Promise 295 | Process-1 296 | Rejected Process-2 297 | ``` 298 | 299 | 300 | ### Promise.always 301 | 302 | **Promise.always()** always receive both **fulfilled** and **rejected** results. 303 | 304 | ```Java 305 | public class ExampleAlways { 306 | 307 | public static void main(String[] args) { 308 | Func func2OutReject = (action, data) -> { 309 | action.reject("I send REJECT"); 310 | //action.resolve("I send RESOLVE"); 311 | }; 312 | Func func2ReceiveAlways = (action, data) -> { 313 | System.out.println("Received:" + data); 314 | action.resolve(); 315 | }; 316 | Promise.resolve() 317 | .then(func2OutReject) 318 | .always(func2ReceiveAlways) 319 | .start(); 320 | } 321 | } 322 | ``` 323 | 324 | **Diagram:** 325 | 326 | 327 | **Result** 328 | 329 | ``` 330 | Received:I send REJECT 331 | ``` 332 | 333 | ### Promise.all 334 | 335 | Execute multiple promises at the same time, and after all executions are complete, move to the next processing with then 336 | 337 | - Execute multiple promises simultaneously and wait until all the execution is finished before proceeding. 338 | - If all finishes with resolve, execution results will be stored as **java.util.List** in the order of invocation. 339 | - If there is even one rejection, store that rejection reason in the result when the rejection occurs and move on to the next "then". 340 | 341 | ```Java 342 | public class ExampleAll { 343 | public static void main(String[] args) { 344 | Func func1 = (action, data) -> { 345 | Promise.sleep(1000); 346 | System.out.println("func1 running"); 347 | action.resolve("func1-result"); 348 | }; 349 | Func func2 = (action, data) -> { 350 | Promise.sleep(500); 351 | System.out.println("func2 running"); 352 | action.resolve("func2-result"); 353 | }; 354 | Func func3 = (action, data) -> { 355 | Promise.sleep(1500); 356 | System.out.println("func3 running"); 357 | action.resolve("func3-result"); 358 | }; 359 | Func funcGetResult = (action, data) -> { 360 | List resultList = (List) data; 361 | for (int i = 0; i < resultList.size(); i++) { 362 | Object o = resultList.get(i); 363 | System.out.println("No." + (i + 1) + " result is " + o); 364 | } 365 | action.resolve(); 366 | }; 367 | Promise.all(func1, func2, func3) 368 | .always(funcGetResult) 369 | .start(); 370 | } 371 | } 372 | ``` 373 | 374 | **Diagram:** 375 | 376 | 377 | 378 | **Result:** 379 | 380 | ``` 381 | func2 running 382 | func1 running 383 | func3 running 384 | No.1 result is func1-result 385 | No.2 result is func2-result 386 | No.3 result is func3-result 387 | ``` 388 | 389 | ### Threading 390 | 391 | It is also possible to execute Promise processing on the specified executor. 392 | Note if you use your own executor, remember to shut it down after use. 393 | If you use your own executor, it will **NOT** be shutdown automatically   394 | 395 | At least one worker thread to be used in Promise.all, 396 | and one thread for overall asynchronous execution, so a total of two or more threads must be needed. 397 | 398 | ```Java 399 | public class Example { 400 | 401 | public static void main(String[] args) { 402 | 403 | final ExecutorService myExecutor = Executors.newFixedThreadPool(5); 404 | 405 | Func func1 = (action, data) -> { 406 | System.out.println("func1 on " + Thread.currentThread().getName()); 407 | action.resolve(); 408 | }; 409 | 410 | Func func2 = (action, data) -> { 411 | System.out.println("func2 on " + Thread.currentThread().getName()); 412 | action.resolve(); 413 | }; 414 | 415 | Func func3 = (action, data) -> { 416 | System.out.println("func3 on " + Thread.currentThread().getName()); 417 | action.resolve(); 418 | }; 419 | 420 | Promise.all(myExecutor, func1, func2, func3) 421 | .then((action, data) -> { 422 | System.out.println("final process on " + Thread.currentThread().getName()); 423 | myExecutor.shutdown();//If you use your own executor, remember to shut it down after use 424 | action.resolve(); 425 | }) 426 | .start(); 427 | } 428 | } 429 | ``` 430 | 431 | **Result:** 432 | 433 | ``` 434 | func1 on pool-1-thread-2 435 | func2 on pool-1-thread-3 436 | func3 on pool-1-thread-4 437 | final process on pool-1-thread-1 438 | ``` 439 | 440 | # SyncPromise 441 | 442 | SyncPromise, as the name implies, is a synchronous promise. 443 | While Promise is executed asynchronously, SyncPromise does NOT move next while it is chained by "then". 444 | All other features are the same as Promise. 445 | 446 | ```Java 447 | public class Example02 { 448 | 449 | public static void main(String[] args) { 450 | Func func1 = (action, data) -> { 451 | new Thread(() -> { 452 | System.out.println("Process-1"); 453 | action.resolve(); 454 | }).start(); 455 | 456 | }; 457 | Func func2 = (action, data) -> { 458 | new Thread(() -> { 459 | System.out.println("Process-2"); 460 | action.resolve(); 461 | }).start(); 462 | 463 | }; 464 | SyncPromise.resolve() 465 | .then(func1) 466 | .then(func2) 467 | .start(); 468 | System.out.println("Hello,Promise"); 469 | } 470 | } 471 | ``` 472 | 473 | **Result:** 474 | 475 | ``` 476 | Process-1 477 | Process-2 478 | Hello,Promise 479 | ``` 480 | 481 | Even if **func1** and **func2** are executed in a thread, 482 | ``System.out.println("Hello,Promise")`` is always executed after that.Because **SyncPromise** is synchronous execution. 483 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 4.0.0 6 | 7 | org.riversun 8 | java-promise 9 | 1.1.0 10 | jar 11 | java-promise 12 | Simple Promise library for java 13 | https://github.com/riversun/java-promise 14 | 15 | 16 | 17 | MIT License 18 | http://www.opensource.org/licenses/mit-license.php 19 | repo 20 | 21 | 22 | 23 | 24 | https://github.com/riversun/java-promise 25 | 26 | scm:git:git://github.com/riversun/java-promise.git 27 | 28 | scm:git:git://github.com/riversun/java-promise.git 29 | 30 | 31 | 32 | 33 | 34 | riversun 35 | Tom Misawa 36 | riversun.org@gmail.com 37 | http://riversun.org 38 | 39 | 40 | 41 | 42 | GitHub 43 | https://github.com/riversun/java-promise/issues 44 | 45 | 46 | 47 | 48 | UTF-8 49 | 50 | 51 | 52 | 53 | junit 54 | junit 55 | 4.12 56 | test 57 | 58 | 59 | 60 | 61 | 62 | ossrh 63 | https://oss.sonatype.org/content/repositories/snapshots 64 | 65 | 66 | ossrh 67 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 68 | 69 | 70 | 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-compiler-plugin 75 | 76 | 77 | 78 | 1.8 79 | 1.8 80 | 81 | 82 | examples/**/* 83 | 84 | 85 | 86 | 87 | org.apache.maven.plugins 88 | maven-gpg-plugin 89 | 1.6 90 | 91 | 92 | sign-artifacts 93 | verify 94 | 95 | sign 96 | 97 | 98 | 99 | 100 | 101 | org.apache.maven.plugins 102 | maven-source-plugin 103 | 2.3 104 | 105 | 106 | attach-sources 107 | 108 | jar 109 | 110 | 111 | 112 | 113 | 114 | org.apache.maven.plugins 115 | maven-javadoc-plugin 116 | 117 | 118 | attach-javadocs 119 | 120 | jar 121 | 122 | 123 | 124 | 125 | none 126 | true 127 | 1.7 128 | protected 129 | UTF-8 130 | UTF-8 131 | UTF-8 132 | -Xdoclint:none 133 | -J-Duser.language=en 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /src/main/java/org/riversun/promise/Action.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018-2019 Tom Misawa(riversun.org@gmail.com) 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | * 22 | */ 23 | package org.riversun.promise; 24 | 25 | /** 26 | * Action object is an argument of Func#run method. 27 | * 28 | * Call action.resolve( [fulfillment value] ) to make the Promise's status fulfilled and move 29 | * on to the next processing(specified by then) with the result(fulfillment value). 30 | * action.resolve("Success"); 31 | * 32 | * Call action.reject( [rejection reason] ) to make the Promise's status rejected and move on to the next processing(specified by then) with the 33 | * result(rejection reason). 34 | * action.reject("Failure"); 35 | * 36 | * Argument is optional, you can call action.resolve() or action.reject() 37 | * action.resolve();//Argument can be omitted 38 | * 39 | * @author Tom Misawa (riversun.org@gmail.com) 40 | * 41 | */ 42 | public interface Action { 43 | 44 | /** 45 | * To make the Promise's status fulfilled and move on to the next processing(specified by then) with the result(fulfillment value). 46 | * 47 | * @param result 48 | */ 49 | public void resolve(Object result); 50 | 51 | /** 52 | * To make the Promise's status fulfilled and move on to the next processing(specified by then) with null result 53 | * 54 | * @param result 55 | */ 56 | public void resolve(); 57 | 58 | /** 59 | * To make the Promise's status rejected and move on to the next processing(specified by then) with reason 60 | * 61 | * @param result 62 | */ 63 | public void reject(Object reason); 64 | 65 | /** 66 | * To make the Promise's status rejected and move on to the next processing(specified by then) with null reason 67 | * 68 | * @param result 69 | */ 70 | public void reject(); 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/org/riversun/promise/Func.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018-2019 Tom Misawa(riversun.org@gmail.com) 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | * 22 | */ 23 | package org.riversun.promise; 24 | 25 | /** 26 | * Func is a java interface equivalent to JavaScript's Function for argument of #then 27 | * 28 | * You can write Func like a JavaScript function. 29 | * I want to show two ways of implementing Func class. 30 | * 31 | * No.1)Write Func object in the normal way. 32 | * 33 | Func function = new Func() { 34 | public void run(Action action, Object data) throws Exception { 35 | System.out.println("Process");//write your logic 36 | action.resolve(); 37 | } 38 | }; 39 | 40 | * 41 | * No.2)Write Func object using lambda expression. 42 | * 43 | Func function = (action, data) -> { 44 | System.out.println("Process");//write your logic 45 | action.resolve(); 46 | }; 47 | 48 | * 49 | * @author Tom Misawa (riversun.org@gmail.com) 50 | * 51 | */ 52 | public interface Func { 53 | public void run(Action action, Object data) throws Exception; 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/riversun/promise/Log.java: -------------------------------------------------------------------------------- 1 | package org.riversun.promise; 2 | 3 | import java.util.logging.Handler; 4 | import java.util.logging.Level; 5 | import java.util.logging.Logger; 6 | 7 | public class Log { 8 | 9 | private static boolean sLogEnabled = false; 10 | 11 | public static boolean isLogEnabled() { 12 | return sLogEnabled; 13 | } 14 | 15 | public static void setLogEnabled(boolean enabled) { 16 | 17 | sLogEnabled = enabled; 18 | 19 | if (enabled) { 20 | System.setProperty("java.util.logging.SimpleFormatter.format", "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %5$s %6$s%n"); 21 | Logger rootLogger = Logger.getLogger(""); 22 | for (Handler handler : rootLogger.getHandlers()) { 23 | handler.setLevel(Level.FINE); 24 | } 25 | rootLogger.setLevel(Level.FINE); 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/riversun/promise/Promise.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018-2019 Tom Misawa(riversun.org@gmail.com) 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | * 22 | */ 23 | package org.riversun.promise; 24 | 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | import java.util.concurrent.CountDownLatch; 28 | import java.util.concurrent.ExecutorService; 29 | import java.util.concurrent.Executors; 30 | import java.util.logging.Logger; 31 | 32 | /** 33 | * 34 | * You can chain asynchronous operations and execute them sequentially 35 | * by using Promise. 36 | * 37 | * Of course, not only asynchronous operations but also synchronous operations 38 | * can be chained. 39 | * 40 | * Example Code 41 | * 42 | Promise.resolve() 43 | .then((action, data) -> { 44 | new Thread(() -> { 45 | // Do something 46 | System.out.println("process-1"); 47 | action.resolve("result-1"); 48 | }).start(); 49 | }) 50 | .then((action, data) -> { 51 | new Thread(() -> { 52 | // Do something 53 | try { 54 | Thread.sleep(1000); 55 | } catch (InterruptedException e) { 56 | } 57 | System.out.println("process-2"); 58 | action.resolve("result-1"); 59 | }).start(); 60 | 61 | }) 62 | .then((action, data) -> { 63 | new Thread(() -> { 64 | // Do something 65 | System.out.println("process-3"); 66 | action.resolve("result-1"); 67 | }).start(); 68 | 69 | }); 70 | 71 | Result in: 72 | process-1 73 | process-2 74 | process-3 75 | 76 | * 77 | * 78 | * @author Tom Misawa (riversun.org@gmail.com) 79 | */ 80 | public class Promise implements Thennable { 81 | 82 | private static final Logger LOGGER = Logger.getLogger(Promise.class.getName()); 83 | private static final String TAG = Promise.class.getSimpleName(); 84 | 85 | private Status mStatus; 86 | 87 | public String mName = ""; 88 | private ExecutorService mExecutor = null; 89 | private boolean mIsExecutorAutoShutdown = true; 90 | private Promise mFounder = null; 91 | private Promise mParentPromise = null; 92 | 93 | private Promise mPreviousPromise; 94 | private Promise mNextPromise; 95 | 96 | private Promise mOnFulfilled = null; 97 | private Promise mOnRejected = null; 98 | 99 | private Object mResult; 100 | private Func mFunc; 101 | 102 | public Promise() { 103 | mStatus = Status.PENDING; 104 | } 105 | 106 | private String getName() { 107 | return mName; 108 | } 109 | 110 | public Promise(String name, Func func, ExecutorService executor) { 111 | this(); 112 | if (name == null) { 113 | name = this.toString(); 114 | } 115 | 116 | this.mName = name; 117 | this.mFunc = func; 118 | if (executor != null) { 119 | this.mExecutor = executor; 120 | this.mIsExecutorAutoShutdown = false; 121 | } 122 | 123 | LOGGER.fine(TAG + " " + getName() + " construct() func=" + mFunc + ", executor=" + executor); 124 | 125 | } 126 | 127 | public Promise(String name, ExecutorService executor) { 128 | this(name, null, executor); 129 | } 130 | 131 | public Promise(ExecutorService executor) { 132 | this(null, null, executor); 133 | } 134 | 135 | public Promise(Func func, ExecutorService executor) { 136 | this(null, func, executor); 137 | } 138 | 139 | public Promise(String name, Func func) { 140 | this(name, func, null); 141 | } 142 | 143 | public Promise(Func func) { 144 | this(null, func, null); 145 | } 146 | 147 | public ExecutorService createExecutor() { 148 | final ExecutorService executor = Executors.newCachedThreadPool(); 149 | LOGGER.fine(TAG + " " + getName() + "#createExecutor created executor=" + executor); 150 | return executor; 151 | } 152 | 153 | /** 154 | * Specify the operation to be performed after the promise processing. 155 | * In this method,you can specify only "Func" instead of doing "new 156 | * Promise(func)".You may omit "new Promise(func)". 157 | * 158 | * @param onFulfilled,[onRejected] 159 | * @return Promise 160 | */ 161 | @Override 162 | public Promise then(Func... funcs) { 163 | 164 | LOGGER.fine(TAG + " " + getName() + "#then(funcs) funcs=" + funcs); 165 | 166 | final List promiseList = new ArrayList(); 167 | if (funcs != null) { 168 | for (Func func : funcs) { 169 | final Promise promise; 170 | if (func == null) { 171 | promise = null; 172 | } else { 173 | promise = new Promise(func); 174 | } 175 | promiseList.add(promise); 176 | } 177 | } 178 | 179 | return then(promiseList.toArray(new Promise[0])); 180 | } 181 | 182 | @Override 183 | public Promise always(Thennable promise) { 184 | LOGGER.fine(TAG + " " + getName() + "#always promise=" + promise); 185 | return then(promise, promise); 186 | } 187 | 188 | @Override 189 | public Promise always(Func func) { 190 | final Promise promise = new Promise(func); 191 | return always(promise); 192 | } 193 | 194 | @Override 195 | public Promise then(Thennable... promises) { 196 | LOGGER.fine(TAG + " " + getName() + "#then(promises) promises=" + promises); 197 | Thennable onFulfilled = null; 198 | Thennable onRejected = null; 199 | 200 | if (promises != null && promises.length > 0) { 201 | onFulfilled = promises[0]; 202 | if (promises.length > 1) { 203 | onRejected = promises[1]; 204 | } 205 | } 206 | // Create executor at first access 207 | if (mExecutor == null) { 208 | mExecutor = createExecutor(); 209 | LOGGER.fine(TAG + " " + getName() + "#then(promises) executor for ths promise is created=" + mExecutor); 210 | } 211 | 212 | // Remember "ancestor" promise at first access 213 | if (mFounder == null) { 214 | mFounder = Promise.this; 215 | } 216 | 217 | mNextPromise = createNextPromise("NextPromise-of-" + mName + ")", (Promise) onFulfilled, (Promise) onRejected); 218 | LOGGER.fine(TAG + " " + 219 | getName() + "#then(promises) createNextPromise from onFulfilled=" + onFulfilled + " and onRejected=" + onRejected + " result next promise =" + mNextPromise); 220 | 221 | // Warning:If you don't "ignite" after all "#then"s called, an inconsistency will occur. 222 | // Do not call ignite before returning all mNextPromise by all of "#then"s 223 | // So I added the "Promise#start" method to start reliably after calling all "then". 224 | // ignite();//<=bad practice 225 | 226 | return mNextPromise; 227 | } 228 | 229 | @Override 230 | public Promise start() { 231 | LOGGER.fine(TAG + " " + getName() + "#start mFounder=" + mFounder); 232 | mFounder.ignite(); 233 | return Promise.this; 234 | } 235 | 236 | /** 237 | * Run first procedure 238 | */ 239 | private void ignite() { 240 | 241 | LOGGER.fine(TAG + " " + getName() + "#ignite mPreviousPromise=" + mPreviousPromise); 242 | 243 | if (mPreviousPromise == null) { 244 | 245 | // first "then" call 246 | LOGGER.fine(TAG + " " + getName() + "#ignite first call! on " + Thread.currentThread()); 247 | 248 | runOnThread(new Runnable() { 249 | @Override 250 | public void run() { 251 | try { 252 | LOGGER.fine(TAG + " " + getName() + "#ignite first call doNext mNextPromise=" + mNextPromise + " mResult=" + mResult); 253 | doNext(mNextPromise, mResult); 254 | } catch (Exception e) { 255 | if (mExecutor != null && mIsExecutorAutoShutdown) { 256 | mExecutor.shutdown(); 257 | } 258 | e.printStackTrace(); 259 | } 260 | } 261 | }); 262 | } 263 | } 264 | 265 | private void invokeFunction(final Object previousPromiseResult) { 266 | 267 | try { 268 | 269 | Promise.this.mFunc.run(new Action() { 270 | @Override 271 | public void resolve(Object result) { 272 | mStatus = Status.FULFILLED; 273 | onFinish(result); 274 | } 275 | 276 | @Override 277 | public void reject(Object result) { 278 | mStatus = Status.REJECTED; 279 | onFinish(result); 280 | } 281 | 282 | @Override 283 | public void resolve() { 284 | resolve(null); 285 | } 286 | 287 | @Override 288 | public void reject() { 289 | reject(null); 290 | } 291 | }, previousPromiseResult); 292 | 293 | } catch (Exception e) { 294 | // e.printStackTrace(); 295 | mStatus = Status.REJECTED; 296 | onFinish(e); 297 | 298 | } 299 | 300 | } 301 | 302 | private void onFinish(Object result) { 303 | 304 | final Object crrResult = result; 305 | 306 | final Promise nextPromise = mParentPromise.mNextPromise; 307 | 308 | LOGGER.fine(TAG + " " + getName() + "#onFinish result=" + result + " nextPromise=" + nextPromise); 309 | 310 | if (nextPromise != null) { 311 | doNext(nextPromise, crrResult); 312 | } else { 313 | // means "nextPromise == null" 314 | 315 | // Since there is no next promise,it means that the execution is the last here. 316 | // So shut down the executor 317 | 318 | if (mParentPromise.mIsExecutorAutoShutdown) { 319 | LOGGER.fine(TAG + " " + getName() + "#onFinish executor(" + mParentPromise.mExecutor + ") SHUTDOWN!!"); 320 | mParentPromise.mExecutor.shutdown(); 321 | } else { 322 | LOGGER.fine(TAG + " " + getName() + "#onFinish executor(" + mParentPromise.mExecutor + ") It is a phase to SHUTDOWN, but does NOT SHUTDOWN because the original executor is set."); 323 | } 324 | } 325 | } 326 | 327 | private void doNext(Promise nextPromise, Object crrResult) { 328 | LOGGER.fine(TAG + " " + getName() + "#doNext mStatus=" + mStatus + " crrResult=" + crrResult); 329 | if (Log.isLogEnabled() && crrResult != null && crrResult instanceof Exception) { 330 | LOGGER.fine(TAG + " " + getName() + "#doNext rejection detected."); 331 | ((Exception) crrResult).printStackTrace(); 332 | } 333 | switch (mStatus) { 334 | case FULFILLED: 335 | 336 | if (nextPromise.mOnFulfilled == null) { 337 | // Skip if resolve is not explicitly set 338 | final Promise skipPromise = new Promise("skipper(fulfilled)", new Func() { 339 | @Override 340 | public void run(Action action, Object data) { 341 | // skip 342 | action.resolve(data); 343 | } 344 | }); 345 | // If there is no next promise to receive the "fulfilled" of the previous promise, 346 | // Also, move to the next to continue "then" chain. 347 | nextPromise.mOnFulfilled = skipPromise; 348 | nextPromise.mOnFulfilled.populateParentPromise(nextPromise); 349 | } 350 | 351 | try { 352 | LOGGER.fine(TAG + " " + getName() + " RUNNING " + nextPromise.mOnFulfilled.getName() + " crrResult=" + crrResult + " on " + Thread.currentThread()); 353 | nextPromise.mOnFulfilled.invokeFunction(crrResult); 354 | } catch (Exception e) { 355 | e.printStackTrace(); 356 | } 357 | break; 358 | case REJECTED: 359 | if (nextPromise.mOnRejected == null) { 360 | // Skip if reject is not explicitly set 361 | final Promise skipPromise = new Promise("skipper(rejected)", new Func() { 362 | @Override 363 | public void run(Action action, Object data) { 364 | // skip 365 | action.reject(data); 366 | } 367 | }); 368 | // If there is no next promise to receive the "rejected" of the previous promise, 369 | // Also, move to the next to continue "then" chain. 370 | nextPromise.mOnRejected = skipPromise; 371 | nextPromise.mOnRejected.populateParentPromise(nextPromise); 372 | } 373 | 374 | nextPromise.mOnRejected.invokeFunction(crrResult); 375 | break; 376 | case PENDING: 377 | throw new RuntimeException("Cannot proceed operation with PENDING promise, please call Promise.resolve to start chain."); 378 | } 379 | } 380 | 381 | private Promise createNextPromise(String promiseName, Promise onFulfilled, Promise onRejected) { 382 | 383 | final Promise nextPromise = new Promise(promiseName, (Func) null); 384 | 385 | nextPromise.mExecutor = Promise.this.mExecutor; 386 | nextPromise.mIsExecutorAutoShutdown = Promise.this.mIsExecutorAutoShutdown; 387 | 388 | nextPromise.mFounder = Promise.this.mFounder; 389 | nextPromise.mPreviousPromise = Promise.this; 390 | 391 | if (onFulfilled != null) { 392 | nextPromise.mOnFulfilled = onFulfilled; 393 | nextPromise.mOnFulfilled.populateParentPromise(nextPromise); 394 | } 395 | 396 | if (onRejected != null) { 397 | nextPromise.mOnRejected = onRejected; 398 | nextPromise.mOnRejected.populateParentPromise(nextPromise); 399 | } 400 | return nextPromise; 401 | } 402 | 403 | private void populateParentPromise(Promise parentPromise) { 404 | mParentPromise = parentPromise; 405 | mPreviousPromise = parentPromise.mPreviousPromise; 406 | mExecutor = parentPromise.mExecutor; 407 | mIsExecutorAutoShutdown = parentPromise.mIsExecutorAutoShutdown; 408 | mFounder = parentPromise.mFounder; 409 | } 410 | 411 | public void runOnThread(Runnable r) { 412 | mExecutor.submit(r); 413 | } 414 | 415 | @Override 416 | public Status getStatus() { 417 | return mStatus; 418 | } 419 | 420 | @Override 421 | public Object getValue() { 422 | return mResult; 423 | } 424 | 425 | /** 426 | * Returns a Promise object that is fulfilled with a given data and specify executor 427 | * 428 | * @param data 429 | * @param executor 430 | * @return 431 | */ 432 | public static Promise resolve(Object data, ExecutorService executor) { 433 | LOGGER.fine(TAG + " " + "Promise.resolve data=" + data + " executor=" + executor); 434 | final Promise promise = new Promise("Promise.Resolve.Created", executor); 435 | promise.mStatus = Status.FULFILLED; 436 | promise.mResult = data; 437 | return promise; 438 | } 439 | 440 | /** 441 | * Returns a Promise object that is fulfilled with a given data 442 | * 443 | * @param data 444 | * @return 445 | */ 446 | public static Promise resolve(Object data) { 447 | return resolve(data, null); 448 | } 449 | 450 | /** 451 | * Returns a Promise object that is resolved with null value 452 | * 453 | * @param data 454 | * @return 455 | */ 456 | public static Promise resolve() { 457 | return resolve(null, null); 458 | } 459 | 460 | /** 461 | * Returns a Promise object that is rejected with a given reason. 462 | * 463 | * @param reason 464 | * @return 465 | */ 466 | public static Promise reject(Object reason) { 467 | final Promise promise = new Promise(); 468 | promise.mStatus = Status.REJECTED; 469 | promise.mResult = reason; 470 | return promise; 471 | } 472 | 473 | /** 474 | * Returns a Promise object that is rejected with null reason. 475 | * 476 | * @param reason 477 | * @return 478 | */ 479 | public static Promise reject() { 480 | return reject(null); 481 | } 482 | 483 | private static final class Holder { 484 | public Object result; 485 | public boolean rejected = false; 486 | } 487 | 488 | /** 489 | * Promise.all waits for all fulfillments (or the first rejection). 490 | * 491 | * Promise.all is rejected if any of the elements are rejected. 492 | * For example, 493 | * if you pass in four promises that resolve after a sleep and one promise 494 | * that rejects immediately, then Promise.all will reject immediately. 495 | * 496 | * Since each promise is executed on its own worker thread, the 497 | * execution of the promise itself continues on the worker thread. 498 | * But, once reject received, Promise.all will move on to then when it receives 499 | * reject even if the worker thread is moving 500 | * 501 | * If fulfilled, all results are returned as "List" at 502 | * {@link Func#run(Action, List)} method. 503 | * 504 | * If rejected, only rejected promise results will be returned as "Error" at 505 | * {@link Func#run(Action, Error)} method. 506 | * 507 | * @param promises 508 | * @return 509 | * 510 | */ 511 | public static Promise all(Thennable... promises) { 512 | ExecutorService executor = null; 513 | return all(executor, promises); 514 | } 515 | 516 | public static Promise all(final ExecutorService executor, Thennable... promises) { 517 | 518 | LOGGER.fine(TAG + " " + "Promise.all executor=" + executor + " promises=" + promises); 519 | 520 | final ExecutorService _executor; 521 | 522 | if (executor == null) { 523 | _executor = Executors.newCachedThreadPool(); 524 | } else { 525 | _executor = executor; 526 | } 527 | 528 | if (promises == null || promises.length == 0) { 529 | // If an empty iterable is passed, then this method returns an 530 | // already resolved promise. 531 | return Promise.resolve(); 532 | } 533 | 534 | final List resultList = new ArrayList(); 535 | final List resultHolderList = new ArrayList(); 536 | 537 | // build workers(=children of Promise.all) promise name 538 | final StringBuilder sbWorkersPromise = new StringBuilder(); 539 | sbWorkersPromise.append("WorkersPromise["); 540 | for (Thennable _promise : promises) { 541 | final Promise srcPromise = (Promise) _promise; 542 | sbWorkersPromise.append(srcPromise.getName() + ":"); 543 | } 544 | sbWorkersPromise.append("]"); 545 | final String nameOfWorkersPromise = sbWorkersPromise.toString(); 546 | 547 | final Func funcWorkers = new Func() { 548 | 549 | public String toString() { 550 | return sbWorkersPromise.toString() + "'s function"; 551 | } 552 | 553 | @Override 554 | public void run(Action _action, Object data) throws Exception { 555 | 556 | final CountDownLatch latch = new CountDownLatch(promises.length); 557 | 558 | final Holder rejectedHolder = new Holder(); 559 | 560 | for (Thennable _promise : promises) { 561 | 562 | final Promise srcPromise = (Promise) _promise; 563 | 564 | LOGGER.fine(TAG + " " + "Promise.all add promise=" + srcPromise.getName()); 565 | 566 | final Holder resultHolder = new Holder(); 567 | resultHolderList.add(resultHolder); 568 | 569 | final Promise workerPromise = new Promise(srcPromise.getName() + ".Starter", _executor); 570 | workerPromise.mStatus = Status.FULFILLED; 571 | 572 | workerPromise.then(srcPromise).then( 573 | // fulfilled 574 | new Promise( 575 | "Promise.all [FULFILLED promise of " + srcPromise.getName() + "]", 576 | new Func() { 577 | @Override 578 | public void run(Action action, Object data) throws Exception { 579 | resultHolder.result = data; 580 | action.resolve(); 581 | LOGGER.fine(TAG + " " + "Promise.all " + srcPromise.getName() + " FULFILLED on " + Thread.currentThread()); 582 | latch.countDown(); 583 | } 584 | }), 585 | // rejected 586 | new Promise( 587 | "Promise.all [REJECTED promise of " + srcPromise.getName() + "]", 588 | new Func() { 589 | @Override 590 | public void run(Action action, Object data) throws Exception { 591 | rejectedHolder.rejected = true; 592 | rejectedHolder.result = data; 593 | resultHolder.result = data; 594 | action.resolve(); 595 | 596 | LOGGER.fine(TAG + " " + "Promise.all " + srcPromise.getName() + " REJECTED on " + Thread.currentThread()); 597 | 598 | // Countdown latches to cancel to move forward even if there is something else thread running 599 | for (int i = 0; i < promises.length; i++) { 600 | latch.countDown(); 601 | } 602 | } 603 | })) 604 | .start(); 605 | } 606 | 607 | latch.await(); 608 | 609 | if (executor == null) { 610 | // automatically shutdown on Promise.all 611 | _executor.shutdown(); 612 | } else { 613 | // The user needs to shutdown the executor 614 | } 615 | 616 | final boolean isRejected = rejectedHolder.rejected; 617 | final Object rejectedResultObject = rejectedHolder.result; 618 | 619 | for (Holder holder : resultHolderList) { 620 | resultList.add(holder.result); 621 | } 622 | 623 | if (isRejected) { 624 | _action.reject(rejectedResultObject); 625 | } else { 626 | _action.resolve(resultList); 627 | } 628 | } 629 | };// end of func 630 | 631 | final Promise workersPromise = new Promise(nameOfWorkersPromise, funcWorkers); 632 | 633 | final Promise starterOfWorkersPromise = new Promise(nameOfWorkersPromise + ".Starter", _executor); 634 | starterOfWorkersPromise.mStatus = Status.FULFILLED; 635 | 636 | return starterOfWorkersPromise.then(workersPromise); 637 | } 638 | 639 | /** 640 | * Promise.all waits for all fulfillments (or the first rejection). 641 | * 642 | * Promise.all is rejected if any of the elements are rejected. 643 | * For example, 644 | * if you pass in four funcs that resolve after a sleep and one func 645 | * that rejects immediately, then Promise.all will reject immediately. 646 | * 647 | * Since each func is executed on its own worker thread, the 648 | * execution of the func itself continues on the worker thread. 649 | * But, once reject received, Promise.all will move on to then when it receives 650 | * reject even if the worker thread is moving 651 | * 652 | * If fulfilled, all results are returned as "List" at 653 | * {@link Func#run(Action, List)} method. 654 | * 655 | * If rejected, only rejected func results will be returned as "Error" at 656 | * {@link Func#run(Action, Error)} method. 657 | * 658 | * @param funcs 659 | * @return 660 | * 661 | */ 662 | 663 | public static Promise all(Func... funcs) { 664 | return all(null, funcs); 665 | } 666 | 667 | public static Promise all(ExecutorService executor, Func... funcs) { 668 | if (funcs == null || funcs.length == 0) { 669 | final Object data = null; 670 | return Promise.resolve(data, executor); 671 | } 672 | 673 | final List promiseList = new ArrayList(); 674 | if (funcs != null) { 675 | 676 | for (Func func : funcs) { 677 | final Promise promise; 678 | if (func == null) { 679 | promise = null; 680 | } else { 681 | promise = new Promise(func); 682 | } 683 | 684 | promiseList.add(promise); 685 | } 686 | } 687 | return Promise.all(executor, promiseList.toArray(new Promise[0])); 688 | } 689 | 690 | public static void sleep(long millis) { 691 | try { 692 | Thread.sleep(millis); 693 | } catch (InterruptedException e) { 694 | e.printStackTrace(); 695 | } 696 | } 697 | 698 | } 699 | -------------------------------------------------------------------------------- /src/main/java/org/riversun/promise/PromiseException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018-2019 Tom Misawa(riversun.org@gmail.com) 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | * 22 | */ 23 | package org.riversun.promise; 24 | 25 | @SuppressWarnings("serial") 26 | public class PromiseException extends Exception { 27 | 28 | private Object mValue; 29 | 30 | PromiseException(Object value) { 31 | mValue = value; 32 | } 33 | 34 | Object getValue() { 35 | return mValue; 36 | } 37 | } -------------------------------------------------------------------------------- /src/main/java/org/riversun/promise/Status.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018-2019 Tom Misawa(riversun.org@gmail.com) 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | * 22 | */ 23 | package org.riversun.promise; 24 | 25 | public enum Status { 26 | PENDING, FULFILLED, REJECTED 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/riversun/promise/SyncPromise.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018-2019 Tom Misawa(riversun.org@gmail.com) 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | * 22 | */ 23 | package org.riversun.promise; 24 | 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | import java.util.concurrent.Callable; 28 | import java.util.concurrent.ExecutionException; 29 | import java.util.concurrent.ExecutorService; 30 | import java.util.concurrent.Executors; 31 | import java.util.concurrent.Future; 32 | import java.util.concurrent.Semaphore; 33 | 34 | /** 35 | * 36 | * You can chain asynchronous operations and execute them sequentially 37 | * by using Promise. 38 | * 39 | * Of course, not only asynchronous operations but also synchronous operations 40 | * can be chained. 41 | * 42 | * Example Code 43 | * 44 | SyncPromise.resolve() 45 | .then((action, data) -> { 46 | new Thread(() -> { 47 | // Do something 48 | System.out.println("process-1"); 49 | action.resolve("result-1"); 50 | }).start(); 51 | }) 52 | .then((action, data) -> { 53 | new Thread(() -> { 54 | // Do something 55 | try { 56 | Thread.sleep(1000); 57 | } catch (InterruptedException e) { 58 | } 59 | System.out.println("process-2"); 60 | action.resolve("result-1"); 61 | }).start(); 62 | 63 | }) 64 | .then((action, data) -> { 65 | new Thread(() -> { 66 | // Do something 67 | System.out.println("process-3"); 68 | action.resolve("result-1"); 69 | }).start(); 70 | 71 | }); 72 | 73 | Result in: 74 | process-1 75 | process-2 76 | process-3 77 | 78 | * 79 | * 80 | * @author Tom Misawa (riversun.org@gmail.com) 81 | */ 82 | public class SyncPromise implements Thennable { 83 | 84 | private final Func mFunc; 85 | 86 | private Status mStatus = Status.PENDING; 87 | private Object mResult; 88 | private SyncPromise mNextPromise; 89 | 90 | public SyncPromise() { 91 | mFunc = null; 92 | } 93 | 94 | /** 95 | * 96 | * Unlike javascript promises, even if executor is passed, 97 | * it will NOT be executed immediately. 98 | * 99 | * If you want to start a series of execution-flows using Promise, 100 | * USE "Promise.resolve().then([YOUR PROMISE])" or 101 | * "Promise.reject().then({YOUR_PROMISE)". 102 | * 103 | * @param executor 104 | */ 105 | public SyncPromise(Func executor) { 106 | mFunc = executor; 107 | } 108 | 109 | /** 110 | * Specify the operation to be performed after the promise processing. 111 | * In this method,you can specify only "Func" instead of doing "new 112 | * Promise(func)".You may omit "new Promise(func)". 113 | * 114 | * @param onFulfilled,[onRejected] 115 | * @return Promise 116 | */ 117 | @Override 118 | public SyncPromise then(Func... funcs) { 119 | final List promiseList = new ArrayList(); 120 | if (funcs != null) { 121 | for (Func func : funcs) { 122 | SyncPromise promise = new SyncPromise(func); 123 | promiseList.add(promise); 124 | } 125 | } 126 | return then(promiseList.toArray(new SyncPromise[0])); 127 | } 128 | 129 | /** 130 | * Specify the operation to be performed after the promise processing. 131 | * 132 | * @param promises 133 | * @return 134 | */ 135 | @Override 136 | public SyncPromise then(Thennable... promises) { 137 | Thennable onFulfilled = null; 138 | Thennable onRejected = null; 139 | 140 | if (promises != null && promises.length > 0) { 141 | 142 | onFulfilled = promises[0]; 143 | if (promises.length > 1) { 144 | onRejected = promises[1]; 145 | } 146 | } else { 147 | throw new RuntimeException("Please set at least one Promise."); 148 | } 149 | 150 | if (this.mStatus == Status.FULFILLED) { 151 | mNextPromise = (SyncPromise) onFulfilled; 152 | 153 | if (mNextPromise != null && mNextPromise.mFunc != null) { 154 | this.invokeFunction(mNextPromise.mFunc, this.mResult); 155 | } else { 156 | // Skip if resolve is not explicitly set 157 | final SyncPromise skipPromise = new SyncPromise(new Func() { 158 | @Override 159 | public void run(Action action, Object data) { 160 | action.resolve(data); 161 | } 162 | }); 163 | mNextPromise = skipPromise; 164 | this.invokeFunction(mNextPromise.mFunc, this.mResult); 165 | return mNextPromise; 166 | } 167 | } 168 | 169 | if (this.mStatus == Status.REJECTED) { 170 | mNextPromise = (SyncPromise) onRejected; 171 | if (mNextPromise != null && mNextPromise.mFunc != null) { 172 | this.invokeFunction(mNextPromise.mFunc, this.mResult); 173 | } else { 174 | 175 | // Following should only be displayed if there was no then to handle rejects at the very end 176 | // System.err.println("Unhandled promise rejection.Please handle rejection with #then(,[rejection-handler])"); 177 | 178 | // Skip if reject function is not explicitly set 179 | final SyncPromise skipPromise = new SyncPromise(new Func() { 180 | @Override 181 | public void run(Action action, Object data) { 182 | action.reject(data); 183 | } 184 | }); 185 | mNextPromise = skipPromise; 186 | this.invokeFunction(mNextPromise.mFunc, this.mResult); 187 | } 188 | } 189 | 190 | return mNextPromise; 191 | } 192 | 193 | @Override 194 | public SyncPromise always(Func func) { 195 | final SyncPromise promise = new SyncPromise(func); 196 | return always(promise); 197 | } 198 | 199 | @Override 200 | public SyncPromise always(Thennable promise) { 201 | return then(promise, promise); 202 | } 203 | 204 | @Override 205 | public SyncPromise start() { 206 | return SyncPromise.this; 207 | } 208 | 209 | /** 210 | * Invoke function object 211 | * 212 | * @param func 213 | * @param previousPromiseResult 214 | */ 215 | private void invokeFunction(Func func, Object previousPromiseResult) { 216 | 217 | // Semaphore for blocking func execution until resolve or reject is called 218 | final Semaphore semaphore = new Semaphore(0); 219 | 220 | try { 221 | func.run(new Action() { 222 | @Override 223 | public void resolve(Object result) { 224 | mNextPromise.mResult = result; 225 | mNextPromise.mStatus = Status.FULFILLED; 226 | 227 | // Releases a permit, returning it to the semaphore. 228 | semaphore.release(); 229 | } 230 | 231 | @Override 232 | public void reject(Object result) { 233 | mNextPromise.mResult = result; 234 | mNextPromise.mStatus = Status.REJECTED; 235 | 236 | // Releases a permit, returning it to the semaphore. 237 | semaphore.release(); 238 | } 239 | 240 | @Override 241 | public void resolve() { 242 | resolve(null); 243 | } 244 | 245 | @Override 246 | public void reject() { 247 | reject(null); 248 | } 249 | }, previousPromiseResult); 250 | } catch (Exception e) { 251 | // Exception is treated as reject 252 | mNextPromise.mResult = e; 253 | mNextPromise.mStatus = Status.REJECTED; 254 | 255 | // Releases a permit, returning it to the semaphore. 256 | semaphore.release(); 257 | } 258 | 259 | try { 260 | // Use semaphores to wait for resolve or reject execution 261 | // Acquires a permit from this semaphore, blocking until semaphore is available, 262 | // or the thread is interrupted. 263 | semaphore.acquire(); 264 | } catch (InterruptedException e) { 265 | // Interruption is treated as reject 266 | e.printStackTrace(); 267 | mNextPromise.mResult = null; 268 | // mNextPromise.mIsRejected = true; 269 | mNextPromise.mStatus = Status.REJECTED; 270 | } 271 | 272 | } 273 | 274 | public Status getStatus() { 275 | return mStatus; 276 | } 277 | 278 | public Object getValue() { 279 | return mResult; 280 | } 281 | 282 | /** 283 | * Returns a Promise object that is resolved with a given value 284 | * 285 | * @param data 286 | * @return 287 | */ 288 | public static SyncPromise resolve(Object data) { 289 | final SyncPromise promise = new SyncPromise(); 290 | promise.mStatus = Status.FULFILLED; 291 | promise.mResult = data; 292 | return promise; 293 | } 294 | 295 | /** 296 | * Returns a Promise object that is resolved with null value 297 | * 298 | * @param data 299 | * @return 300 | */ 301 | public static SyncPromise resolve() { 302 | return resolve(null); 303 | } 304 | 305 | /** 306 | * Returns a Promise object that is rejected with a given reason. 307 | * 308 | * @param reason 309 | * @return 310 | */ 311 | public static SyncPromise reject(Object reason) { 312 | final SyncPromise promise = new SyncPromise(); 313 | promise.mStatus = Status.REJECTED; 314 | promise.mResult = reason; 315 | return promise; 316 | } 317 | 318 | /** 319 | * Returns a Promise object that is rejected with null reason. 320 | * 321 | * @param reason 322 | * @return 323 | */ 324 | public static SyncPromise reject() { 325 | return reject(null); 326 | } 327 | 328 | /** 329 | * Promise.all waits for all fulfillments (or the first rejection). 330 | * 331 | * Promise.all is rejected if any of the elements are rejected. 332 | * For example, 333 | * if you pass in four promises that resolve after a sleep and one promise 334 | * that rejects immediately, then Promise.all will reject immediately. 335 | * 336 | * Since each promise is executed on its own worker thread, the 337 | * execution of the promise itself continues on the worker thread. 338 | * But, once reject received, Promise.all will move on to then when it receives 339 | * reject even if the worker thread is moving 340 | * 341 | * If fulfilled, all results are returned as "List" at 342 | * {@link Func#run(Action, List)} method. 343 | * 344 | * If rejected, only rejected promise results will be returned as "Error" at 345 | * {@link Func#run(Action, Error)} method. 346 | * 347 | * @param promises 348 | * @return 349 | * 350 | */ 351 | public static SyncPromise all(Thennable... promises) { 352 | 353 | if (promises == null || promises.length == 0) { 354 | // If an empty iterable is passed, then this method returns an 355 | // already resolved promise. 356 | return SyncPromise.resolve(); 357 | } 358 | 359 | final ExecutorService executor = Executors.newCachedThreadPool(); 360 | 361 | final List> futureList = new ArrayList>(); 362 | 363 | final List resultList = new ArrayList(); 364 | 365 | try {// Make sure executor.shutdown is called at "try-finally". 366 | 367 | for (Thennable promise : promises) { 368 | 369 | final Callable callable = new Callable() { 370 | 371 | @Override 372 | public SyncPromise call() throws Exception { 373 | SyncPromise result = SyncPromise.resolve().then(promise); 374 | if (result.getStatus() == Status.REJECTED) { 375 | // throw exception to interrupt 376 | throw new PromiseException(result.getValue()); 377 | } 378 | return result; 379 | } 380 | }; 381 | 382 | final Future future = executor.submit(callable); 383 | futureList.add(future); 384 | } 385 | } finally { 386 | executor.shutdown(); 387 | } 388 | 389 | boolean rejected = false; 390 | Object rejectedError = null; 391 | 392 | for (Future f : futureList) { 393 | try { 394 | SyncPromise result = f.get(); 395 | resultList.add(result.getValue()); 396 | } catch (InterruptedException e) { 397 | // exit loop 398 | break; 399 | } catch (ExecutionException e) { 400 | 401 | final Throwable cause = e.getCause(); 402 | 403 | if (cause instanceof PromiseException) { 404 | PromiseException pe = (PromiseException) cause; 405 | rejectedError = pe.getValue(); 406 | rejected = true; 407 | } 408 | // exit loop 409 | break; 410 | } 411 | } 412 | 413 | if (rejected) { 414 | return SyncPromise.reject(rejectedError); 415 | } else { 416 | return SyncPromise.resolve(resultList); 417 | } 418 | } 419 | 420 | /** 421 | * Promise.all waits for all fulfillments (or the first rejection). 422 | * 423 | * Promise.all is rejected if any of the elements are rejected. 424 | * For example, 425 | * if you pass in four funcs that resolve after a sleep and one func 426 | * that rejects immediately, then Promise.all will reject immediately. 427 | * 428 | * Since each func is executed on its own worker thread, the 429 | * execution of the func itself continues on the worker thread. 430 | * But, once reject received, Promise.all will move on to then when it receives 431 | * reject even if the worker thread is moving 432 | * 433 | * If fulfilled, all results are returned as "List" at 434 | * {@link Func#run(Action, List)} method. 435 | * 436 | * If rejected, only rejected func results will be returned as "Error" at 437 | * {@link Func#run(Action, Error)} method. 438 | * 439 | * @param funcs 440 | * @return 441 | * 442 | */ 443 | public static SyncPromise all(Func... funcs) { 444 | if (funcs == null || funcs.length == 0) { 445 | return SyncPromise.resolve(); 446 | } 447 | 448 | final List promiseList = new ArrayList(); 449 | if (funcs != null) { 450 | for (Func func : funcs) { 451 | SyncPromise promise = new SyncPromise(func); 452 | promiseList.add(promise); 453 | } 454 | } 455 | return SyncPromise.all(promiseList.toArray(new SyncPromise[0])); 456 | } 457 | 458 | public static void sleep(long sleepMillis) { 459 | try { 460 | Thread.sleep(sleepMillis); 461 | } catch (InterruptedException e) { 462 | e.printStackTrace(); 463 | } 464 | } 465 | } -------------------------------------------------------------------------------- /src/main/java/org/riversun/promise/Thennable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018-2019 Tom Misawa(riversun.org@gmail.com) 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | * 22 | */ 23 | package org.riversun.promise; 24 | 25 | /** 26 | * Common interface of Promise. Aim to chain with then 27 | * 28 | * @author Tom Misawa (riversun.org@gmail.com) 29 | * 30 | */ 31 | public interface Thennable { 32 | 33 | public Thennable then(Func... funcs); 34 | 35 | public Thennable then(Thennable... promises); 36 | 37 | public Thennable always(Func func); 38 | 39 | public Thennable always(Thennable promise); 40 | 41 | public Thennable start(); 42 | 43 | public Status getStatus(); 44 | 45 | public Object getValue(); 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/org/riversun/promise/AppTest.java: -------------------------------------------------------------------------------- 1 | package org.riversun.promise; 2 | 3 | import org.junit.runner.RunWith; 4 | import org.junit.runners.Suite; 5 | import org.junit.runners.Suite.SuiteClasses; 6 | 7 | /** 8 | * Test suite for java-promise
9 | * 10 | * Tom Misawa (riversun.org@gmail.com) 11 | */ 12 | @RunWith(Suite.class) 13 | @SuiteClasses({ 14 | TestPromiseSync.class, TestPromiseAsync.class, 15 | TestPromiseAllSync.class, TestPromiseAllAsync.class 16 | }) 17 | public class AppTest { 18 | 19 | } -------------------------------------------------------------------------------- /src/test/java/org/riversun/promise/TestPromiseAllAsync.java: -------------------------------------------------------------------------------- 1 | package org.riversun.promise; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.concurrent.CountDownLatch; 8 | import java.util.concurrent.ExecutorService; 9 | import java.util.concurrent.Executors; 10 | 11 | import org.junit.Test; 12 | 13 | /** 14 | * Tests for Promise.all
15 | * 16 | * Tom Misawa (riversun.org@gmail.com) 17 | */ 18 | public class TestPromiseAllAsync extends TestPromiseAllTestCase { 19 | private CountDownLatch mLatch; 20 | 21 | public void sync(Integer... integers) { 22 | if (integers != null && integers.length > 0) { 23 | mLatch = new CountDownLatch(integers[0]); 24 | } else { 25 | mLatch = new CountDownLatch(1); 26 | } 27 | } 28 | 29 | public void consume() { 30 | mLatch.countDown(); 31 | } 32 | 33 | public void await() { 34 | try { 35 | mLatch.await(); 36 | } catch (InterruptedException e) { 37 | e.printStackTrace(); 38 | } 39 | } 40 | 41 | @Override 42 | public Thennable PromiseAll(Thennable... promises) { 43 | return Promise.all(promises); 44 | } 45 | 46 | @Override 47 | public Thennable PromiseAll(Func... funcs) { 48 | return Promise.all(funcs); 49 | } 50 | 51 | @Override 52 | public Thennable PromiseResolve(Object data) { 53 | return Promise.resolve(data); 54 | } 55 | 56 | @Override 57 | public Thennable PromiseReject(Object data) { 58 | return Promise.reject(data); 59 | } 60 | 61 | /** 62 | * Test that all worker threads executed in promise.all are created from the same thread pool. 63 | */ 64 | @Test 65 | public void test_promise_all_threads_is_from_the_same_thread() { 66 | sync(); 67 | final List threadNames = new ArrayList(); 68 | Func func1 = (action, data) -> { 69 | final String result = Thread.currentThread().getName(); 70 | action.resolve(result); 71 | }; 72 | Promise p1 = new Promise("1st", func1); 73 | 74 | Func func2 = (action, data) -> { 75 | final String result = Thread.currentThread().getName(); 76 | action.resolve(result); 77 | }; 78 | Promise p2 = new Promise("2nd", func2); 79 | 80 | Func func3 = (action, data) -> { 81 | final String result = Thread.currentThread().getName(); 82 | action.resolve(result); 83 | }; 84 | Promise p3 = new Promise("3rd", func3); 85 | 86 | Promise.all( 87 | p1, p2, p3) 88 | .then((action, data) -> { 89 | final String myThread = Thread.currentThread().getName(); 90 | for (Object o : (List) data) { 91 | threadNames.add((String) o); 92 | } 93 | threadNames.add(myThread); 94 | action.resolve(); 95 | consume(); 96 | }) 97 | .start(); 98 | await(); 99 | 100 | // make sure all threads are from the same thread-pool 101 | for (int i = 0; i < threadNames.size() - 1; i++) { 102 | final String threadName = threadNames.get(i); 103 | final String threadPoolName = threadName.substring(0, threadName.lastIndexOf("-")); 104 | 105 | final String threadNameNext = threadNames.get(i + 1); 106 | final String threadPoolNameNext = threadName.substring(0, threadNameNext.lastIndexOf("-")); 107 | 108 | assertEquals(threadPoolName, threadPoolNameNext); 109 | } 110 | } 111 | 112 | // Original test for Promise 113 | /** 114 | * Make sure that user's executor is guaranteed to work. 115 | */ 116 | @SuppressWarnings("unchecked") 117 | @Test 118 | public void test_promise_all_specify_original_thread_pool() { 119 | 120 | // At least one worker thread to be used in Promise.all, 121 | // and one thread for overall asynchronous execution, so a total of two or more threads must be needed. 122 | final int NUM_OF_THREADS = 2; 123 | final ExecutorService myExecutor = Executors.newFixedThreadPool(NUM_OF_THREADS); 124 | 125 | sync(); 126 | final List threadNames = new ArrayList(); 127 | Func func1 = (action, data) -> { 128 | final String result = Thread.currentThread().getName(); 129 | action.resolve(result); 130 | }; 131 | Promise p1 = new Promise("1st", func1); 132 | 133 | Func func2 = (action, data) -> { 134 | final String result = Thread.currentThread().getName(); 135 | action.resolve(result); 136 | }; 137 | Promise p2 = new Promise("2nd", func2); 138 | 139 | Func func3 = (action, data) -> { 140 | final String result = Thread.currentThread().getName(); 141 | action.resolve(result); 142 | }; 143 | Promise p3 = new Promise("3rd", func3); 144 | 145 | Promise.all(myExecutor, 146 | p1, p2, p3) 147 | .then((action, data) -> { 148 | final String myThread = Thread.currentThread().getName(); 149 | for (Object o : (List) data) { 150 | threadNames.add((String) o); 151 | } 152 | threadNames.add(myThread); 153 | action.resolve(); 154 | // If you specify your own executor, you have to shut it down at the end 155 | myExecutor.shutdown(); 156 | consume(); 157 | }) 158 | .start(); 159 | await(); 160 | 161 | // make sure all threads are from the same thread-pool 162 | for (int i = 0; i < threadNames.size() - 1; i++) { 163 | final String threadName = threadNames.get(i); 164 | final String threadPoolName = threadName.substring(0, threadName.lastIndexOf("-")); 165 | 166 | final String threadNameNext = threadNames.get(i + 1); 167 | final String threadPoolNameNext = threadName.substring(0, threadNameNext.lastIndexOf("-")); 168 | 169 | assertEquals(threadPoolName, threadPoolNameNext); 170 | } 171 | } 172 | } -------------------------------------------------------------------------------- /src/test/java/org/riversun/promise/TestPromiseAllSync.java: -------------------------------------------------------------------------------- 1 | package org.riversun.promise; 2 | 3 | /** 4 | * Tests for SyncPromise.all
5 | * 6 | * Tom Misawa (riversun.org@gmail.com) 7 | */ 8 | public class TestPromiseAllSync extends TestPromiseAllTestCase { 9 | 10 | public void sync(Integer... integers) { 11 | } 12 | 13 | public void consume() { 14 | } 15 | 16 | public void await() { 17 | } 18 | 19 | @Override 20 | public Thennable PromiseAll(Thennable... promises) { 21 | return SyncPromise.all(promises); 22 | } 23 | 24 | @Override 25 | public Thennable PromiseAll(Func... funcs) { 26 | return SyncPromise.all(funcs); 27 | } 28 | 29 | @Override 30 | public Thennable PromiseResolve(Object data) { 31 | return SyncPromise.resolve(data); 32 | } 33 | 34 | @Override 35 | public Thennable PromiseReject(Object data) { 36 | return SyncPromise.reject(data); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /src/test/java/org/riversun/promise/TestPromiseAllTestCase.java: -------------------------------------------------------------------------------- 1 | package org.riversun.promise; 2 | 3 | import static org.hamcrest.CoreMatchers.hasItems; 4 | import static org.junit.Assert.assertEquals; 5 | import static org.junit.Assert.assertThat; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | import java.util.concurrent.ExecutorService; 11 | import java.util.concurrent.Executors; 12 | 13 | import org.junit.Test; 14 | 15 | /** 16 | * Tests for promise.all
17 | * 18 | * Tom Misawa (riversun.org@gmail.com) 19 | */ 20 | public abstract class TestPromiseAllTestCase { 21 | 22 | public abstract Thennable PromiseAll(Thennable... promises); 23 | 24 | public abstract Thennable PromiseAll(Func... funcs); 25 | 26 | public abstract Thennable PromiseResolve(Object data); 27 | 28 | public Thennable PromiseResolve() { 29 | return PromiseResolve(null); 30 | } 31 | 32 | public abstract Thennable PromiseReject(Object data); 33 | 34 | public Thennable PromiseReject() { 35 | return PromiseReject(null); 36 | } 37 | 38 | public abstract void sync(Integer... counter); 39 | 40 | public abstract void consume(); 41 | 42 | public abstract void await(); 43 | 44 | private static class ObjectHolder { 45 | public Object data; 46 | } 47 | 48 | /** 49 | * Test PromiseAll with empty arg 50 | */ 51 | @Test 52 | public void test_promiseAll_empty_chain() { 53 | sync(); 54 | final ObjectHolder holder = new ObjectHolder(); 55 | PromiseAll(new Func[] {}) 56 | .then((action, obj) -> { 57 | holder.data = obj; 58 | action.resolve(); 59 | consume(); 60 | }) 61 | .start(); 62 | await(); 63 | assertEquals(null, holder.data); 64 | 65 | } 66 | 67 | /** 68 | * Test PromiseAll with empty arg 69 | */ 70 | @Test 71 | public void test_promiseAllp_empty_chain() { 72 | sync(); 73 | final ObjectHolder holder = new ObjectHolder(); 74 | PromiseAll(new Promise[] {}) 75 | .then((action, obj) -> { 76 | holder.data = obj; 77 | action.resolve(); 78 | consume(); 79 | }) 80 | .start(); 81 | await(); 82 | assertEquals(null, holder.data); 83 | 84 | } 85 | 86 | /** 87 | * Test PromiseAll #1-1 typical sync chain 88 | */ 89 | @SuppressWarnings("unchecked") 90 | @Test 91 | public void test_promiseAll_chain() { 92 | sync(); 93 | final ObjectHolder holder = new ObjectHolder(); 94 | PromiseAll( 95 | (action, data) -> { 96 | action.resolve("1"); 97 | }, 98 | (action, data) -> { 99 | action.resolve("2"); 100 | }, 101 | (action, data) -> { 102 | action.resolve("3"); 103 | }) 104 | .then((Action action, Object data) -> { 105 | holder.data = data; 106 | 107 | action.resolve(); 108 | consume(); 109 | }).start(); 110 | await(); 111 | assertEquals(true, holder.data instanceof List); 112 | assertThat((List) holder.data, hasItems("1", "2", "3")); 113 | assertEquals((List) holder.data, Arrays.asList("1", "2", "3"));// check order 114 | 115 | } 116 | 117 | /** 118 | * Test PromiseAll #1-1 typical sync chain(null data) 119 | */ 120 | @SuppressWarnings("unchecked") 121 | @Test 122 | public void test_promiseAll_returns_null_chain() { 123 | sync(); 124 | final ObjectHolder holder = new ObjectHolder(); 125 | PromiseAll( 126 | (action, data) -> { 127 | action.resolve(); 128 | }, 129 | (action, data) -> { 130 | action.resolve(); 131 | }, 132 | (action, data) -> { 133 | action.resolve(); 134 | }) 135 | .then((Action action, Object data) -> { 136 | holder.data = data; 137 | action.resolve(); 138 | consume(); 139 | }) 140 | .start(); 141 | await(); 142 | assertEquals(true, holder.data instanceof List); 143 | assertEquals((List) holder.data, Arrays.asList(null, null, null)); 144 | } 145 | 146 | /** 147 | * Test PromiseAll #2-1 thread-execution in the chain 148 | */ 149 | @SuppressWarnings("unchecked") 150 | @Test 151 | public void test_promiseAll_with_thread_chain() { 152 | sync(); 153 | final ObjectHolder holder = new ObjectHolder(); 154 | PromiseAll( 155 | (action, data) -> { 156 | new Thread(() -> { 157 | Promise.sleep(500); 158 | action.resolve("1"); 159 | }).start(); 160 | }, 161 | (action, data) -> { 162 | action.resolve("2"); 163 | }, 164 | (action, data) -> { 165 | Promise.sleep(100); 166 | action.resolve("3"); 167 | }) 168 | .then((Action action, Object data) -> { 169 | holder.data = data; 170 | 171 | action.resolve(); 172 | consume(); 173 | }) 174 | .start(); 175 | await(); 176 | assertEquals(true, holder.data instanceof List); 177 | assertThat((List) holder.data, hasItems("1", "2", "3")); 178 | assertEquals((List) holder.data, Arrays.asList("1", "2", "3"));// check order 179 | } 180 | 181 | /** 182 | * Test PromiseAll #2-2 thread-execution in the chain 183 | */ 184 | @SuppressWarnings("unchecked") 185 | @Test 186 | public void test_promiseAll_with_thread_full_chain() { 187 | sync(); 188 | final ObjectHolder holder = new ObjectHolder(); 189 | PromiseAll( 190 | (action, data) -> { 191 | new Thread(() -> { 192 | Promise.sleep(500); 193 | action.resolve("1"); 194 | }).start(); 195 | }, 196 | (action, data) -> { 197 | new Thread(() -> { 198 | Promise.sleep(1000); 199 | action.resolve("2"); 200 | }).start(); 201 | }, 202 | (action, data) -> { 203 | new Thread(() -> { 204 | Promise.sleep(1500); 205 | action.resolve("3"); 206 | }).start(); 207 | }) 208 | .then((Action action, Object data) -> { 209 | holder.data = data; 210 | action.resolve(); 211 | consume(); 212 | }) 213 | .start(); 214 | await(); 215 | assertEquals(true, holder.data instanceof List); 216 | assertThat((List) holder.data, hasItems("1", "2", "3")); 217 | assertEquals((List) holder.data, Arrays.asList("1", "2", "3"));// check order 218 | } 219 | 220 | /** 221 | * Test PromiseAll #3-1 test reject by action#reject 222 | */ 223 | @Test 224 | public void test_promiseAll_with_rejection_chain() { 225 | sync(); 226 | final ObjectHolder holder = new ObjectHolder(); 227 | PromiseAll( 228 | (action, data) -> { 229 | Promise.sleep(500); 230 | action.resolve("1"); 231 | }, 232 | (action, data) -> { 233 | action.resolve("2"); 234 | }, 235 | (action, data) -> { 236 | action.reject("ERROR"); 237 | }) 238 | .then(null, (Action action, Object data) -> { 239 | holder.data = data; 240 | 241 | action.resolve(); 242 | consume(); 243 | }) 244 | .start(); 245 | await(); 246 | assertEquals(false, holder.data instanceof List); 247 | assertEquals("ERROR", holder.data); 248 | } 249 | 250 | /** 251 | * Test PromiseAll #3-2 test reject by action#reject 252 | */ 253 | @Test 254 | public void test_promiseAll_with_multi_rejection_chain() { 255 | sync(); 256 | final ObjectHolder holder = new ObjectHolder(); 257 | PromiseAll( 258 | (action, data) -> { 259 | Promise.sleep(50); 260 | action.reject("ERROR1"); 261 | }, 262 | (action, data) -> { 263 | Promise.sleep(150); 264 | action.reject("ERROR2"); 265 | }, 266 | (action, data) -> { 267 | Promise.sleep(500); 268 | action.reject("ERROR3"); 269 | }) 270 | .then(null, (Action action, Object data) -> { 271 | holder.data = data; 272 | action.resolve(); 273 | consume(); 274 | }) 275 | .start(); 276 | await(); 277 | assertEquals(false, holder.data instanceof List); 278 | assertEquals("ERROR1", holder.data); 279 | 280 | } 281 | 282 | /** 283 | * Test PromiseAll #4 test reject by exception 284 | */ 285 | @Test 286 | public void test_promiseAll_with_rejection_by_exception() { 287 | sync(); 288 | final ObjectHolder holder = new ObjectHolder(); 289 | PromiseAll( 290 | (action, data) -> { 291 | Promise.sleep(500); 292 | action.resolve("1"); 293 | }, 294 | (action, data) -> { 295 | throw new Exception("My Exception"); 296 | }, 297 | (action, data) -> { 298 | action.resolve("3"); 299 | }) 300 | .then(null, (Action action, Object data) -> { 301 | holder.data = data; 302 | action.resolve(); 303 | consume(); 304 | }) 305 | .start(); 306 | await(); 307 | assertEquals(true, holder.data instanceof Exception); 308 | assertEquals("My Exception", ((Exception) holder.data).getMessage()); 309 | } 310 | 311 | /** 312 | * Test Promise.resolve 313 | */ 314 | @Test 315 | public void test_promiseResolve() { 316 | sync(); 317 | final ObjectHolder holder = new ObjectHolder(); 318 | PromiseResolve().then( 319 | (action, data) -> { 320 | holder.data = data; 321 | action.resolve(); 322 | consume(); 323 | }) 324 | .start(); 325 | await(); 326 | assertEquals(null, holder.data); 327 | } 328 | 329 | /** 330 | * Test Promise.resolve #2 check resolve(String) 331 | */ 332 | @Test 333 | public void test_promiseResolve_check_arg_string() { 334 | sync(); 335 | final ObjectHolder holder = new ObjectHolder(); 336 | PromiseResolve("str").then( 337 | (action, data) -> { 338 | holder.data = data; 339 | action.resolve(); 340 | consume(); 341 | }) 342 | .start(); 343 | await(); 344 | assertEquals("str", holder.data); 345 | } 346 | 347 | /** 348 | * Test Promise.resolve #3 check resolve(String[]) 349 | */ 350 | @Test 351 | public void test_promiseResolve_check_arg_array_of_string() { 352 | sync(); 353 | final ObjectHolder holder = new ObjectHolder(); 354 | PromiseResolve(new String[] { "str1", "str2" }).then( 355 | (action, data) -> { 356 | holder.data = data; 357 | action.resolve(); 358 | consume(); 359 | }) 360 | .start(); 361 | await(); 362 | 363 | assertEquals(Arrays.asList(new String[] { "str1", "str2" }), Arrays.asList((String[]) holder.data)); 364 | } 365 | 366 | /** 367 | * Test Promise.reject 368 | */ 369 | @Test 370 | public void test_promiseReject() { 371 | sync(); 372 | final ObjectHolder holder = new ObjectHolder(); 373 | PromiseReject().then(null, 374 | (action, data) -> { 375 | holder.data = data; 376 | action.reject(); 377 | consume(); 378 | }) 379 | .start(); 380 | await(); 381 | assertEquals(null, holder.data); 382 | } 383 | 384 | /** 385 | * Test Promise.reject #2 check reject(String) 386 | */ 387 | @Test 388 | public void test_promiseReject_check_arg_string() { 389 | sync(); 390 | final ObjectHolder holder = new ObjectHolder(); 391 | PromiseReject("str").then(null, 392 | (action, data) -> { 393 | holder.data = data; 394 | action.reject(); 395 | consume(); 396 | }) 397 | .start(); 398 | await(); 399 | assertEquals("str", holder.data); 400 | } 401 | 402 | /** 403 | * Test Promise.reject #3 check reject(String[]) 404 | */ 405 | @Test 406 | public void test_promiseReject_check_arg_array_of_string() { 407 | sync(); 408 | final ObjectHolder holder = new ObjectHolder(); 409 | PromiseReject(new String[] { "str1", "str2" }).then( 410 | null, 411 | (action, data) -> { 412 | holder.data = data; 413 | action.reject(); 414 | consume(); 415 | }) 416 | .start(); 417 | await(); 418 | 419 | assertEquals(Arrays.asList(new String[] { "str1", "str2" }), Arrays.asList((String[]) holder.data)); 420 | } 421 | 422 | } -------------------------------------------------------------------------------- /src/test/java/org/riversun/promise/TestPromiseAsync.java: -------------------------------------------------------------------------------- 1 | package org.riversun.promise; 2 | 3 | import java.util.concurrent.CountDownLatch; 4 | 5 | /** 6 | * Tests for Promise
7 | * 8 | * Tom Misawa (riversun.org@gmail.com) 9 | */ 10 | public class TestPromiseAsync extends TestPromiseTestCase { 11 | 12 | @Override 13 | public Thennable PromiseResolve(Object value) { 14 | return Promise.resolve(value); 15 | } 16 | 17 | @Override 18 | public Thennable PromiseReject(Object value) { 19 | return Promise.reject(value); 20 | } 21 | 22 | private CountDownLatch mLatch; 23 | 24 | public void sync(Integer... integers) { 25 | if (integers != null && integers.length > 0) { 26 | mLatch = new CountDownLatch(integers[0]); 27 | } else { 28 | mLatch = new CountDownLatch(1); 29 | } 30 | } 31 | 32 | public void consume() { 33 | mLatch.countDown(); 34 | } 35 | 36 | public void await() { 37 | try { 38 | mLatch.await(); 39 | } catch (InterruptedException e) { 40 | e.printStackTrace(); 41 | } 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /src/test/java/org/riversun/promise/TestPromiseSync.java: -------------------------------------------------------------------------------- 1 | package org.riversun.promise; 2 | 3 | /** 4 | * Tests for SyncPromise
5 | * 6 | * Tom Misawa (riversun.org@gmail.com) 7 | */ 8 | public class TestPromiseSync extends TestPromiseTestCase { 9 | 10 | @Override 11 | public Thennable PromiseResolve(Object value) { 12 | return SyncPromise.resolve(value); 13 | 14 | } 15 | 16 | @Override 17 | public Thennable PromiseReject(Object value) { 18 | return SyncPromise.reject(value); 19 | } 20 | 21 | @Override 22 | public void sync(Integer... integers) { 23 | } 24 | 25 | @Override 26 | public void consume() { 27 | } 28 | 29 | @Override 30 | public void await() { 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/test/java/org/riversun/promise/TestPromiseTestCase.java: -------------------------------------------------------------------------------- 1 | package org.riversun.promise; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import org.junit.Test; 6 | 7 | /** 8 | * Tests for promise
9 | * 10 | * Tom Misawa (riversun.org@gmail.com) 11 | */ 12 | public abstract class TestPromiseTestCase { 13 | 14 | public abstract Thennable PromiseResolve(Object value); 15 | 16 | public Thennable PromiseResolve() { 17 | return PromiseResolve(null); 18 | } 19 | 20 | public abstract Thennable PromiseReject(Object value); 21 | 22 | public Thennable PromiseReject() { 23 | return PromiseReject(null); 24 | } 25 | 26 | public abstract void sync(Integer... counter); 27 | 28 | public abstract void consume(); 29 | 30 | public abstract void await(); 31 | 32 | public void sleep(long millis) { 33 | try { 34 | Thread.sleep(millis); 35 | } catch (InterruptedException e) { 36 | // TODO Auto-generated catch block 37 | e.printStackTrace(); 38 | } 39 | } 40 | 41 | /** 42 | * Test Promise.resolve() 43 | */ 44 | @Test 45 | public void test_promise_resolve_with_null() { 46 | 47 | final StringBuilder sb = new StringBuilder(); 48 | sync(); 49 | PromiseResolve().then((action, data) -> { 50 | action.resolve(); 51 | }).always((action, data) -> { 52 | if (data == null) { 53 | sb.append("data is null"); 54 | } 55 | action.resolve(); 56 | consume(); 57 | }).start(); 58 | await(); 59 | assertEquals("data is null", sb.toString()); 60 | } 61 | 62 | /** 63 | * Test Promise.resolve() 64 | */ 65 | @Test 66 | public void test_promise_resolve_chain() { 67 | 68 | final StringBuilder sb = new StringBuilder(); 69 | 70 | sync(); 71 | 72 | PromiseResolve("0") 73 | .then((action, data) -> { 74 | sb.append(data); 75 | sb.append("1"); 76 | action.reject(); 77 | }) 78 | .always((action, data) -> { 79 | 80 | action.resolve(); 81 | consume(); 82 | }).start(); 83 | 84 | await(); 85 | assertEquals("01", sb.toString()); 86 | } 87 | 88 | /** 89 | * Test Promise.reject() 90 | */ 91 | @Test 92 | public void test_promise_reject_with_null() { 93 | final StringBuilder sb = new StringBuilder(); 94 | sync(); 95 | PromiseReject() 96 | .then(null, (action, data) -> { 97 | if (data == null) { 98 | sb.append("data is null"); 99 | } 100 | action.resolve(); 101 | consume(); 102 | }) 103 | .start(); 104 | await(); 105 | assertEquals("data is null", sb.toString()); 106 | } 107 | 108 | /** 109 | * Test Promise.reject() 110 | */ 111 | @Test 112 | public void test_promise_reject_chain() { 113 | 114 | final StringBuilder sb = new StringBuilder(); 115 | sync(); 116 | PromiseReject("0") 117 | .then(null, (action, data) -> { 118 | sb.append(data); 119 | sb.append("1"); 120 | action.reject(); 121 | }) 122 | .always((action, data) -> { 123 | action.resolve(); 124 | consume(); 125 | }) 126 | .start(); 127 | await(); 128 | assertEquals("01", sb.toString()); 129 | 130 | } 131 | 132 | /** 133 | * Test to propagate resolve result 134 | */ 135 | @Test 136 | public void test_resolve_result_chain() { 137 | 138 | final StringBuilder sb = new StringBuilder(); 139 | sync(); 140 | PromiseResolve("0") 141 | .then((action, data) -> { 142 | sb.append(data); 143 | action.resolve("1"); 144 | }) 145 | .then((action, data) -> { 146 | sb.append(data); 147 | action.resolve("2"); 148 | }) 149 | .then((action, data) -> { 150 | sb.append(data); 151 | action.resolve(); 152 | }) 153 | .always((action, data) -> { 154 | 155 | action.resolve(); 156 | consume(); 157 | }) 158 | .start(); 159 | await(); 160 | assertEquals("012", sb.toString()); 161 | 162 | } 163 | 164 | @Test 165 | public void test_reject_result_chain() { 166 | 167 | final StringBuilder sb = new StringBuilder(); 168 | sync(); 169 | PromiseReject("0") 170 | .then(null, (action, data) -> { 171 | sb.append(data); 172 | action.reject("1"); 173 | }) 174 | .then(null, (action, data) -> { 175 | sb.append(data); 176 | action.reject("2"); 177 | }) 178 | .then(null, (action, data) -> { 179 | sb.append(data); 180 | action.reject(); 181 | }) 182 | .always((action, data) -> { 183 | 184 | action.resolve(); 185 | consume(); 186 | }) 187 | .start(); 188 | await(); 189 | assertEquals("012", sb.toString()); 190 | 191 | } 192 | 193 | /** 194 | * Make sure skip null of resolve func on then 195 | */ 196 | @Test 197 | public void test_reject_result_skip_resolve_chain() { 198 | 199 | final StringBuilder sb = new StringBuilder(); 200 | sync(); 201 | PromiseReject("0") 202 | .then(null, (action, data) -> { 203 | sb.append(data); 204 | action.resolve("1"); 205 | }) 206 | .then((Func) null, (Func) null) 207 | .then((action, data) -> { 208 | sb.append(data); 209 | action.reject(); 210 | }) 211 | .always((action, data) -> { 212 | action.resolve(); 213 | consume(); 214 | }) 215 | .start(); 216 | await(); 217 | assertEquals("01", sb.toString()); 218 | 219 | } 220 | 221 | /** 222 | * Make sure skip null of reject func on then 223 | */ 224 | @Test 225 | public void test_reject_result_skip_reject_chain() { 226 | 227 | final StringBuilder sb = new StringBuilder(); 228 | sync(); 229 | PromiseReject("0") 230 | .then(null, (action, data) -> { 231 | sb.append(data); 232 | action.reject("1"); 233 | }) 234 | .then((Func) null, (Func) null) 235 | .then(null, (action, data) -> { 236 | sb.append(data); 237 | action.reject(); 238 | }) 239 | .always((action, data) -> { 240 | action.resolve(); 241 | consume(); 242 | }) 243 | .start(); 244 | await(); 245 | assertEquals("01", sb.toString()); 246 | 247 | } 248 | 249 | /** 250 | * Test whether the handling of reject is done properly 251 | */ 252 | @Test 253 | public void test_fail_chain() { 254 | 255 | sync(); 256 | final StringBuilder sb = new StringBuilder(); 257 | 258 | Thennable promiseResolve = PromiseResolve("start"); 259 | 260 | promiseResolve.then((action, data) -> { 261 | sb.append("1"); 262 | action.reject(); 263 | }).then((action, data) -> { 264 | sb.append("2"); 265 | action.resolve(); 266 | }, (action, data) -> { 267 | sb.append("fail"); 268 | action.resolve(); 269 | }).then((action, data) -> { 270 | sb.append("3"); 271 | action.resolve(); 272 | }).always((action, data) -> { 273 | action.resolve(); 274 | consume(); 275 | }) 276 | .start(); 277 | await(); 278 | assertEquals("1fail3", sb.toString()); 279 | 280 | } 281 | 282 | /** 283 | * Test that treats exception as reject 284 | */ 285 | @Test 286 | public void test_exception_chain() { 287 | 288 | final StringBuilder sb = new StringBuilder(); 289 | sync(); 290 | PromiseResolve("start") 291 | .then((action, data) -> { 292 | sb.append("1"); 293 | throw new Exception("ErrorCase"); 294 | }) 295 | .then((action, data) -> { 296 | sb.append("2"); 297 | action.resolve(); 298 | }, (action, error) -> { 299 | Exception e = (Exception) error; 300 | String errMessage = e.getMessage(); 301 | sb.append(errMessage); 302 | action.resolve(); 303 | }) 304 | .then((action, data) -> { 305 | sb.append("3"); 306 | action.resolve(); 307 | }) 308 | .always((action, data) -> { 309 | action.resolve(); 310 | consume(); 311 | }) 312 | .start(); 313 | await(); 314 | assertEquals("1ErrorCase3", sb.toString()); 315 | 316 | } 317 | 318 | /** 319 | * Test sync 320 | */ 321 | @Test 322 | public void test_sync_chain() { 323 | 324 | final StringBuilder sb = new StringBuilder(); 325 | sync(); 326 | PromiseResolve("start") 327 | .then((action, data) -> { 328 | sb.append("1"); 329 | action.resolve(); 330 | }) 331 | .then((action, data) -> { 332 | sb.append("2"); 333 | action.resolve(); 334 | }) 335 | .then((action, data) -> { 336 | sb.append("3"); 337 | action.resolve(); 338 | }) 339 | .always((action, data) -> { 340 | action.resolve(); 341 | consume(); 342 | }) 343 | .start(); 344 | await(); 345 | assertEquals("123", sb.toString()); 346 | 347 | } 348 | 349 | /** 350 | * Test sync with sleep 351 | */ 352 | @Test 353 | public void test_sync_with_sleep_chain() { 354 | 355 | final StringBuilder sb = new StringBuilder(); 356 | sync(); 357 | PromiseResolve("start") 358 | .then((action, data) -> { 359 | sb.append("1"); 360 | sleep(100); 361 | action.resolve(); 362 | }) 363 | .then((action, data) -> { 364 | sb.append("2"); 365 | sleep(100); 366 | action.resolve(); 367 | }) 368 | .then((action, data) -> { 369 | sb.append("3"); 370 | sleep(100); 371 | action.resolve(); 372 | }) 373 | .always((action, data) -> { 374 | action.resolve(); 375 | consume(); 376 | }) 377 | .start(); 378 | await(); 379 | assertEquals("123", sb.toString()); 380 | 381 | } 382 | 383 | /** 384 | * Test to make sure that asynchronous operation is performed correct order. 385 | */ 386 | @Test 387 | public void test_async_chain() { 388 | 389 | final StringBuilder sb = new StringBuilder(); 390 | sync(); 391 | PromiseResolve("start") 392 | .then((action, data) -> { 393 | new Thread(() -> { 394 | sb.append("1"); 395 | sleep(100); 396 | action.resolve(); 397 | }).start(); 398 | 399 | }) 400 | .then((action, data) -> { 401 | new Thread(() -> { 402 | sb.append("2"); 403 | sleep(100); 404 | action.resolve(); 405 | }).start(); 406 | }) 407 | .then((action, data) -> { 408 | new Thread(() -> { 409 | sb.append("3"); 410 | sleep(100); 411 | action.resolve(); 412 | }).start(); 413 | }) 414 | .always((action, data) -> { 415 | action.resolve(); 416 | consume(); 417 | }) 418 | .start(); 419 | await(); 420 | assertEquals("123", sb.toString()); 421 | 422 | } 423 | 424 | /** 425 | * Test to make sure that even sync and async are performed correct order. 426 | */ 427 | @Test 428 | public void test_sync_async_chain() { 429 | 430 | final StringBuilder sb = new StringBuilder(); 431 | sync(); 432 | PromiseResolve("start") 433 | .then((action, data) -> { 434 | new Thread(() -> { 435 | sb.append("1"); 436 | action.resolve(); 437 | }).start(); 438 | }) 439 | .then((action, data) -> { 440 | new Thread(() -> { 441 | sb.append("2"); 442 | sleep(100); 443 | action.resolve(); 444 | }).start(); 445 | }) 446 | .then((action, data) -> { 447 | sb.append("3"); 448 | action.resolve(); 449 | }) 450 | .always((action, data) -> { 451 | consume(); 452 | action.resolve(); 453 | }) 454 | .start(); 455 | await(); 456 | assertEquals("123", sb.toString()); 457 | 458 | } 459 | 460 | /** 461 | * In the case of Promise, execute the same j asynchronously. In the case of SyncPromise, confirm the synchronous execution. 462 | */ 463 | @Test 464 | public void test_concurrent_execution_chain() { 465 | 466 | final int concurrencyNumber = 10; 467 | sync(concurrencyNumber); 468 | StringBuilder[] sbs = new StringBuilder[concurrencyNumber]; 469 | 470 | for (int i = 0; i < concurrencyNumber; i++) { 471 | StringBuilder sb = new StringBuilder(); 472 | sb.append("(" + i + ")"); 473 | sbs[i] = sb; 474 | PromiseResolve("start") 475 | .then((action, data) -> { 476 | new Thread(() -> { 477 | sb.append("1"); 478 | action.resolve(); 479 | }).start(); 480 | }) 481 | .then((action, data) -> { 482 | new Thread(() -> { 483 | sb.append("2"); 484 | // sleep(10); 485 | action.resolve(); 486 | }).start(); 487 | }) 488 | .then((action, data) -> { 489 | sb.append("3"); 490 | action.resolve(); 491 | }) 492 | .always((action, data) -> { 493 | action.resolve(); 494 | consume(); 495 | }) 496 | .start(); 497 | } 498 | await(); 499 | 500 | for (int i = 0; i < concurrencyNumber; i++) { 501 | assertEquals("(" + i + ")" + "123", sbs[i].toString()); 502 | } 503 | 504 | } 505 | } --------------------------------------------------------------------------------