https://github.com/akarnokd/async-enumerable
Prototype Java 9 library based on the asynchronous enumerable concept (where moveNext() returns a task to compose over).
https://github.com/akarnokd/async-enumerable
asynchronous enumerable java
Last synced: about 1 year ago
JSON representation
Prototype Java 9 library based on the asynchronous enumerable concept (where moveNext() returns a task to compose over).
- Host: GitHub
- URL: https://github.com/akarnokd/async-enumerable
- Owner: akarnokd
- License: apache-2.0
- Created: 2017-12-25T12:45:36.000Z (over 8 years ago)
- Default Branch: master
- Last Pushed: 2023-01-30T04:03:11.000Z (over 3 years ago)
- Last Synced: 2025-03-25T04:37:20.307Z (over 1 year ago)
- Topics: asynchronous, enumerable, java
- Language: Java
- Homepage:
- Size: 223 KB
- Stars: 18
- Watchers: 3
- Forks: 1
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# async-enumerable
[](http://codecov.io/github/akarnokd/async-enumerable?branch=master)
[](http://search.maven.org/#search%7Cga%7C1%7Ccom.github.akarnokd%2C%20async-enumerable)
Prototype Java 9 library based on the asynchronous enumerable concept (where moveNext() returns a task to compose over).
### Gradle
```groovy
compile "com.github.akarnokd:async-enumerable:0.6.0"
```
### Getting started
The main entry point is the `hu.akarnokd.asyncenum.AsyncEnumerable` interface with its static factory methods similar to RxJava:
```java
AsyncEnumerable source = AsyncEnumerable.range(1, 10);
AsyncEnumerable strings = AsyncEnumerable.fromArray("a", "b", "c", "d");
```
`AsyncEnumerable` is a deferred cold source, which can be synchronous or asynchronous, the
`enumerator()` has to be called to receive another interface,
`hu.akarnokd.asyncenum.AsyncEnumerator` to be "iterated" over.
```java
AsyncEnumerator enumerator = source.enumerator();
```
The `AsyncEnumerator` defines two methods, `moveNext()` and `current()`. Calling `moveNext()` will instruct the
source to produce the next value but instead of returning a `false` or `true` immediately, the method returns a
`java.util.concurrent.CompletionStage` that is completed with `true` if a value is ready and `false` if no
more values to be expected. In the `true` case, one can read the current value via `current()`.
(Cancelling a sequence is currently partially supported via the `cancel()` method on `AsyncEnumerator`,
but it feels too much Reactive Streams and not like the pre-existing cancellation support in
other async-enumerable libraries.)
```java
CompletionStage stage = enumerator.moveNext();
stage.whenComplete((hasValue, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
if (hasValue) {
System.out.println(enumerator.current());
} else {
System.out.println("Empty source!");
}
})
```
Note that calling `moveNext()` or `current()` during the time the `CompletionStage` hasn't been terminated is an
undefined behavior. Calling `moveNext` after the previous `CompletionStage` returned `false` or an exception is
also undefined behavior.
Therefore, consuming multiple values via a plain for loop doesn't work; one has to call `moveNext` when the previous
`CompletionStage` completed with `true` in a recursively looking pattern. Since some `AsyncEnumerable` chains can
be synchronous, this leads to `StackOverflowError` if not handled properly.
For this purpose, the `forEach()` instance method on `AsyncEnumerable` is available, but given an `AsyncEnumerator`,
the following consumption pattern can be employed:
```java
final class EnumeratorConsumer extends AtomicInteger implements BiConsumer {
final AsyncEnumerator enumerator;
public EnumeratorConsumer(AsyncEnumerator enumerator) {
this.enumerator = enumerator;
}
@Override
public void accept(Boolean hasNext, Throwable error) {
if (error != null) {
// handle error case
return;
}
if (hasNext) {
T value = enumerator.current();
// handle current value
moveNext();
} else {
// handle no more values
}
}
public void moveNext() {
if (getAndIncrement() == 0) {
do {
enumerator.moveNext().whenComplete(this);
} while (decrementAndGet() != 0);
}
}
}
new EnumeratorConsumer(source.enumerator()).moveNext();
```
This is practically the same queue-drain or trampolining logic used throughout RxJava. It is recommended though
to use the combinators and operators of `AsyncEnumerable` instead as working with a sequence of `CompletionStage`
continuations, especially when there are multiple active sequences involved as complications.