{"id":26667116,"url":"https://github.com/joeycumines/java-promises","last_synced_at":"2025-07-06T22:38:15.754Z","repository":{"id":95934065,"uuid":"88479267","full_name":"joeycumines/java-promises","owner":"joeycumines","description":"Writing asynchronous Java 8 code, to the style of JavaScript Promises.","archived":false,"fork":false,"pushed_at":"2017-05-12T07:36:46.000Z","size":433,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-25T19:46:25.883Z","etag":null,"topics":["completionstage","java","java-8","multi-threading","promise"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/joeycumines.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-04-17T06:36:34.000Z","updated_at":"2022-04-11T07:19:17.000Z","dependencies_parsed_at":"2023-09-02T02:18:57.459Z","dependency_job_id":null,"html_url":"https://github.com/joeycumines/java-promises","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/joeycumines/java-promises","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joeycumines%2Fjava-promises","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joeycumines%2Fjava-promises/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joeycumines%2Fjava-promises/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joeycumines%2Fjava-promises/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/joeycumines","download_url":"https://codeload.github.com/joeycumines/java-promises/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joeycumines%2Fjava-promises/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263984965,"owners_count":23539768,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["completionstage","java","java-8","multi-threading","promise"],"created_at":"2025-03-25T19:36:18.907Z","updated_at":"2025-07-06T22:38:15.744Z","avatar_url":"https://github.com/joeycumines.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# java-promises - Writing async code to the style of JavaScript Promises\nAfter deciding to re-learn Java, and without any real knowledge of most Java 8 features but coming from using JavaScript\nfrequently, I was very interested to learn of the `CompletableFuture` and the related `CompletionStage` interface.\nWhen my eyes started hurting immediately after opening the JavaDoc however, I decided it would be a valuable learning\nexperience to implement my own, more friendly implementation.\n\nI wrote this library with the goal of making it something I would be happy to use in production code, and to that end I\nfocused on designing flexible, sensible interfaces, that were well documented and thought out and as simple as they\ncould be, while delivering the functionality I wanted. It's also very close to 100% tested, though I did frequently\nsacrifice test clarity and readability for overall thoroughness. The design is based quite heavily on the ES6 JavaScript\nPromise, with changes where I saw fit.\n\nThis project has taught me a huge amount about generics, thread safety, the JVM, mocks in Java, along with many other\nthings, and I am very happy with the current design. I will be using this library in another project, and making any\nimprovements to this library that I think of - I don't believe I will be changing the interfaces.\n\n## Performance?\nPretty good actually. The benchmarks are low-tech but they were very enlightening, to give a tl;dr:\n\n- The biggest factor in performance for a given problem is the type of threading, e.g. the `Executor` used\n- My initial `Promise` implementation is on par with `CompletableFuture` in terms of performance (variable depending \non the use case), but imo it works far better for blocking logic due to it's design, as how promises are run is much\neasier and less tedious to configure, and much more flexible out of the box.\n- If you replace what could be simple logic with complicated thread switching logic, you're going to have a bad time,\nbut it's the same situation with `CompletableFuture`... `Promise` just makes it much easier to do\n- If you are cognisant of the fact that chaining methods like `Promise.then()` ALL run async (like the async suffixed\nmethods of `CompletionStage`) your code should be performant, if not you are probably using this library wrong.\n\n## Interoperability?\nUse promises within completion stages, write your own promise implementation, use your completion stage implementation\nas a promise, use different promise implementations together, configure how separate promises are run, go for your life.\n\nThough the promises are designed to take advantages of generics in a inherently type safe way, I wrote an implementation\nof chained resolution to an unknown depth, in the same manner as JS promises, provided by `PromiseApi.resolve()`, \nwhich can also detect circular references (something which is not necessary if types are used properly).\n\n## What's the structure look like?\nPretty much you want to look at the interfaces `Promise` and `PromiseFactory`, and you probably want to check out the\nabstract class `PromiseApi` (that requires a `PromiseFactory` implementation), but the api is entirely optional.\n\n## JavaDoc\n[Read the API documentation HERE](https://joeycumines.github.io/java-promises/)\n\n## Changelog\n- 1.0.0 - Initial Release\n    - 1.0.1 - minor change for consistent return types in PromiseApi\n\n## (Bad) Benchmark - 100x Mean + STDDEV (Windows 10 x64)\nOut of interest, I implemented some basic benchmarks (using `System.currentTimeMillis()`), which can be found and run \nvia the `ShittyPerformanceTest` class, using JUnit. In an effort to make it slightly more scientific, I wrote controls\nfor each test, single threaded, and using `CompletableFuture` and/or basic multi threading, and tested 4 separate\nimplementations of my `Promise` and related interfaces, including my initial `PromiseRunnable`, and 3 others that all\nuse `PromiseStage`, a wrapper for `CompletionStage`, which allowed me to test against (in a possibly unfair way),\nthe `CompletableFuture` class, and two other libraries I found on GitHub, \n[lukas-krecan/completion-stage](https://github.com/lukas-krecan/completion-stage) and\n[chrisalice/j8stages](https://github.com/chrisalice/j8stages).\nEach of these promise tests were run with two executors, a cached thread pool, and a common fork join pool, for a total\nof 8 separate iterations of each test, iterations which were run in a randomized order each time, and each time worked \nfrom the same randomized inputs (which the controls also worked from). Promise test codes starting with `3` and `4`\nare using the `CompletableFuture` as a base.\n\nThe tests cases are each one of two scenarios, a brute force tree search, and a request-response style async test, \nboth which are contrived, but which have provided some interesting results. I tried to keep the setup tasks consistent\nand if possible out of the actual time, for each iteration of each test, be it promise or control.\n\nThe test case was run 100 times using windows batch scripting (I originally ran it on Ubuntu using OpenJDK, but I felt\na Windows desktop would be a more fair test), output to text files, then combined using a script I wrote for the\npurpose. The result below was generated from console output, where, after some line normalization, any changed numeric \nvalues where first collated, processed, then re-inserted into their original positions, in the format `|mean (stddev)|`.\nIn terms of the relative performance, running it using OpenJDK had consistent results with this test.\n\n### \"Maze\" benchmark - brute force tree search\nThis test was the first one I wrote, and likely more naive. It consists of a tree structure where each parent has an\narray of child nodes, where the algorithm must find the success leaf node, which is selected pseudo-randomly. To prove\nit has found the success case, the algorithm must find the combined string of each unique node id, in the path from the\nstart to the finish.\n\n#### Single threaded control test\nThe single threaded solution obviously had very variable cost, but still managed to outperform all others, which I can \nonly assume is a combination of the type of task, and not having the overhead in creating objects. If I had run it with \na considerably deeper tree, then there would probably be more benefit to multiple threads. It is also probably related \nto the fairly decent single core speed of the processor used.\n\n#### CompletableFuture and simplistic multi threaded control tests\nThe `CompletableFuture` control test performed the best out of the multi threaded solutions, for reasons I mostly \nattribute to poor test design. The multi threaded control implementation lost by a large margin, however that would seem\nto be a side effect of using a fixed thread pool of `40`, having run a few re-tests, it seems using the fork join common\npool makes it much quicker.\n\nThe `4(breadth) x 11(depth)` maze test was the most enlightening, as all `Promise` implementations performed much worse\nin comparison to the controls. There are a few likely reasons for this, and if I had to guess, I would say that the \ndifference in speed is due to the cost of the promise implementation's more general implementation; if you look at code \nfor the future test, in `MazeTester`, the `CompletableFuture` control logic is very direct. While the `PromiseApi.any`\nimplementation, that was used for all promise tests, does support early exit, it was very likely sub-optimal for this \nparticular case, where there is no internal cost in compute time to even the odds. The promise api must go through quite\na few more callbacks, objects, and threads for each one of the future implementation.\n\n##### After further testing:\nRefactoring the `Promise` test case to more closely match the one for `CompletableFuture` saw the test time decrease for\nthe `4(breadth) x 11(depth)` test by over a 100% across the board, which would support the previous conclusions. This\nchange saw `1_RUNNABLE` come last in several tests of the `10(breadth) x 6(depth)` maze, a test where it's fork-join\nequivalent as well as the `MyFuture` promise came out on top, and which was the quickest test overall.\n\n#### The Promise implementation tests\nBoth my original implementation, and the implementation using the j8stage repo's `MyFuture` class came out on top, \nwith mine edging ahead. Following the same line of inquiry as before, this result supports the conclusion that the cost\nof this test is greatly affected by the number of callbacks, threads, or possibly objects created, as the `PromiseStage` \nimplementation necessitated less direct logic routes, if it was to encapsulate any `CompletionStage` while providing the\nsame `Promise` functionality.\n\n### Request-Response test - Interpreting strings as an equation\nUsing a cool little algorithm I grabbed off StackOverflow, this test emulates a request-response style problem, with\nadjustable and consistent cost of work, and the ability to define the number of requests. This test supports the\nconclusion that the overhead involved with even a very large number of promises matters very little when more processing\nintensive tasks become more relevant. The test with the most costly operation `difficulty 10000 and 1000 connections`,\nwhich result in a work time (per request) averaging on the low end of 10ms, showed that all the asynchronous approaches\nwere on par, as there was no significant difference between any of the methods using the fork join common pool.\n\nIt can also be concluded that the most expensive part of the `Promise` implementation probably comes when using a large\nnumber of dependant promises, to perform complicated logic. The `difficulty 100 and 100000 connections` test supports\nthis as well; the `Promise` implementations perform effectively the same as the `CompletableFuture` control, with only\nrelatively small variations that can be correlated with the type of `Executor` used, and the style of implementation\nbacking it.\n\n### The combined results of 100 consecutive (separate), somewhat primitive benchmarks\nThe results discussed above. It's worth noting that, for the test with the greatest disparity between \n`CompletableFuture`, and any of the `Promise` implementations, adjusting the `Promise` test so that it more closely\nmatched the future control resulted in a considerable time decrease, such that when run on a Thinkpad T450s, using \nOpenJDK on Ubuntu 16.04, the `1_RUNNABLE` test case consistently beat the `CompletableFuture` control, for the same \n`4(breadth) x 11(depth)` maze test.\n\n```\n    --generating maze for 10(breadth) x 6(depth)--\n    --maze generation complete--\n    [control] maze solution single threaded took (ms): |11.37 (4.85)|\n    [control] maze solution multi threaded took (ms): |80.65 (16.6)|\n    [control] maze solution using CompletableFuture took (ms): |82.29 (27.29)|\n    RUNNING: maze test\n    COMPLETED: maze test\n    #|1.92 (1)| 1_RUNNABLE ( |79.76 (29.76)| ms )\n    #|2.18 (1.07)| 2_RUNNABLE_FORK_JOIN_COMMON ( |86.91 (32.17)| ms )\n    #|5.76 (0.85)| 3_STAGE ( |294.17 (40.09)| ms )\n    #|5.71 (0.83)| 4_STAGE_DEFAULT_EXECUTOR ( |294.66 (40.03)| ms )\n    #|7.13 (0.77)| 5_JAVACRUMBS ( |358.48 (40.8)| ms )\n    #|7.4 (0.79)| 6_JAVACRUMBS_FORK_JOIN_COMMON ( |373.84 (44.04)| ms )\n    #|2.94 (0.97)| 7_MYFUTURE ( |120.23 (44.07)| ms )\n    #|2.96 (1.05)| 8_MYFUTURE_FORK_JOIN_COMMON ( |116.28 (41.08)| ms )\n    --generating maze for 4(breadth) x 11(depth)--\n    --maze generation complete--\n    [control] maze solution single threaded took (ms): |72 (40.57)|\n    [control] maze solution multi threaded took (ms): |936.27 (366.66)|\n    [control] maze solution using CompletableFuture took (ms): |385.92 (303.12)|\n    RUNNING: maze test\n    COMPLETED: maze test\n    #|1.95 (1.08)| 1_RUNNABLE ( |1921.45 (316.7)| ms )\n    #|2.05 (1.12)| 2_RUNNABLE_FORK_JOIN_COMMON ( |1953.71 (339.02)| ms )\n    #|5.69 (0.87)| 3_STAGE ( |5259.57 (455.88)| ms )\n    #|5.63 (0.75)| 4_STAGE_DEFAULT_EXECUTOR ( |5220.8 (385.25)| ms )\n    #|7.26 (0.66)| 5_JAVACRUMBS ( |6097.08 (439.79)| ms )\n    #|7.42 (0.65)| 6_JAVACRUMBS_FORK_JOIN_COMMON ( |6183.01 (407.21)| ms )\n    #|3.07 (0.83)| 7_MYFUTURE ( |2166.44 (422.55)| ms )\n    #|2.93 (0.96)| 8_MYFUTURE_FORK_JOIN_COMMON ( |2142.81 (331.68)| ms )\n    -- Running the Request-Response test for difficulty 10000 and 1000 connections\n    -- generating requests\n    -- requests generated\n    [control] on average the actual work should take (ms): 1\n    [control] a single thread served all requests sequentially in (ms): |1660.54 (70.49)|\n    [control] for CompletableFuture on average the work took (ms): |2.85 (0.41)|\n    [control] for CompletableFuture the total time spent was (ms): |462.66 (39.72)|\n    RUNNING: MathRequester test\n    COMPLETED: MathRequester test\n    #|5.66 (1.43)| 1_RUNNABLE ( |499.21 (29.37)| ms )\n    #|3.45 (2.45)| 2_RUNNABLE_FORK_JOIN_COMMON ( |477.07 (40.88)| ms )\n    #|6.01 (1.47)| 3_STAGE ( |503.48 (28.95)| ms )\n    #|2.27 (1.36)| 4_STAGE_DEFAULT_EXECUTOR ( |460.72 (24.35)| ms )\n    #|6.08 (1.65)| 5_JAVACRUMBS ( |507.98 (29.94)| ms )\n    #|4.05 (2.28)| 6_JAVACRUMBS_FORK_JOIN_COMMON ( |484.1 (45.36)| ms )\n    #|5.93 (1.5)| 7_MYFUTURE ( |504.14 (29.24)| ms )\n    #|2.55 (1.34)| 8_MYFUTURE_FORK_JOIN_COMMON ( |463.19 (26.05)| ms )\n    [result] MathRequester time for 1_RUNNABLE was (ms): |8.06 (9.1)|\n    [result] MathRequester time for 2_RUNNABLE_FORK_JOIN_COMMON was (ms): |2.99 (0.17)|\n    [result] MathRequester time for 3_STAGE was (ms): |5.29 (3.42)|\n    [result] MathRequester time for 4_STAGE_DEFAULT_EXECUTOR was (ms): |2.96 (0.2)|\n    [result] MathRequester time for 5_JAVACRUMBS was (ms): |8.91 (11.24)|\n    [result] MathRequester time for 6_JAVACRUMBS_FORK_JOIN_COMMON was (ms): |2.98 (0.28)|\n    [result] MathRequester time for 7_MYFUTURE was (ms): |5.8 (3.71)|\n    [result] MathRequester time for 8_MYFUTURE_FORK_JOIN_COMMON was (ms): |2.99 (0.17)|\n    -- Running the Request-Response test for difficulty 100 and 100000 connections\n    -- generating requests\n    -- requests generated\n    [control] on average the actual work should take (ms): 0\n    [control] a single thread served all requests sequentially in (ms): |1724.58 (114.75)|\n    [control] for CompletableFuture on average the work took (ms): 0\n    [control] for CompletableFuture the total time spent was (ms): |586.15 (56.87)|\n    RUNNING: MathRequester test\n    COMPLETED: MathRequester test\n    #|5.8 (1.63)| 1_RUNNABLE ( |691.48 (115.13)| ms )\n    #|2.04 (1.8)| 2_RUNNABLE_FORK_JOIN_COMMON ( |586.4 (82.8)| ms )\n    #|5.5 (1.93)| 3_STAGE ( |694.54 (101.17)| ms )\n    #|2.7 (1.68)| 4_STAGE_DEFAULT_EXECUTOR ( |606.2 (83.41)| ms )\n    #|5.75 (1.74)| 5_JAVACRUMBS ( |689.86 (83.22)| ms )\n    #|5.08 (1.61)| 6_JAVACRUMBS_FORK_JOIN_COMMON ( |667.62 (96.48)| ms )\n    #|5.8 (1.96)| 7_MYFUTURE ( |715.29 (128.47)| ms )\n    #|3.33 (1.85)| 8_MYFUTURE_FORK_JOIN_COMMON ( |629.27 (104.64)| ms )\n    [result] MathRequester time for 1_RUNNABLE was (ms): 0\n    [result] MathRequester time for 2_RUNNABLE_FORK_JOIN_COMMON was (ms): 0\n    [result] MathRequester time for 3_STAGE was (ms): |0.01 (0.1)|\n    [result] MathRequester time for 4_STAGE_DEFAULT_EXECUTOR was (ms): 0\n    [result] MathRequester time for 5_JAVACRUMBS was (ms): 0\n    [result] MathRequester time for 6_JAVACRUMBS_FORK_JOIN_COMMON was (ms): 0\n    [result] MathRequester time for 7_MYFUTURE was (ms): 0\n    [result] MathRequester time for 8_MYFUTURE_FORK_JOIN_COMMON was (ms): 0\nTotal time: |53.13 (1.27)| secs\n```\n\n## License - SEE LICENSE\nCopyright 2017 Joseph Cumines\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoeycumines%2Fjava-promises","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjoeycumines%2Fjava-promises","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoeycumines%2Fjava-promises/lists"}