I have multiple observables doing network calls and I need to emit error from combiner observable only when all observables emitted error. If at least one observable completes then the result should be passed.
My current stream functions like this:
Observable.fromIterable(list)
.flatMap{networkObservable}
.reduce{combine result of all network observables to same list}
I could just do:
Observable.fromIterable(list)
.flatMap{networkObservable.onErrorReturnItem(errorItem)}
.reduce{check if all items are error items and return error item otherwise combine result of
all network observables to same list}
But I wanted to know if there's a better way to handle this case.
If I understand correctly, if there is at least one item from any of the networkObservables, errors should be ignored, right? In this case, you could detect the onNexts emitted, then in the error handler, suppress the error:
Observable.using(
() -> new AtomicBoolean(),
hasItem -> Observable.fromIterable(list)
.flatMap(v -> networkObservable, true) // delay errors
.doOnNext(v -> {
if (!hasItem.get()) {
hasItem.set(true);
}
})
.onErrorResumeNext(error -> {
if (hasItem.get()) {
return Observable.empty();
}
return Observable.error(error);
}),
() -> { } // no need to clean up the AtomicBoolean
)
Related
Java version : 11
I have a List, which contains many sublist and for each sublist I want to perform certain transformation/operations.
I want to perform this operation in non-blocking asynchronous fashion, so I am using CompletableFuture.
This is my operation:
public static List<String> convertBusinessObjectJson(List<BusinessObject> businessObjList) {
List<Either> eitherValueOrException = {//omitted logic to convert to json}
return eitherValueOrException;
}
It returns a List of Either Objects, where Either holds, either runtime exception thrown by conversion logic or String result when conversion is successful.
This is my caller code:
mainList.forEach(sublist -> {
CompletableFuture<List<Either>> listCompletableFuture = CompletableFuture.supplyAsync(() -> FutureImpl.convertBusinessObjectJson(sublist));
});
Once the CompletableFuture<List<Either>> listCompletableFuture is received, I want to chain the operation,
As in
take CompletableFuture<List<Either>> listCompletableFuture, take exceptions only from list and, perform certain operation
take CompletableFuture<List<Either>> listCompletableFuture, take results only from list and, perform certain operation
Something like this (pseudo code):
mainList.forEach(sublist -> {
CompletableFuture<List<Either>> listCompletableFuture = CompletableFuture.supplyAsync(() -> FutureImpl.convertDSRowToJson(subDSRowList));
listCompletableFuture.thenApply(//function which pushes exception to say kafka)
listCompletableFuture.thenApply(//function which pushes result to say database)
});
Can it be done?
Any help is much appreciated :)
You could try smth like this:
var futureList = mainList.stream()
.map(sublist -> CompletableFuture.supplyAsync(() -> FutureImpl.convertBusinessObjectJson(sublist)))
.collect(Collectors.toList());
The above would collect a list of CompletableFutures. Now what needs to happen is we need to wait for the completion of all those futures. We do this by:
var joinedFutureList = futureList.stream()
.map(objectCompletableFuture -> {
try {
return objectCompletableFuture.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}).collect(Collectors.toList());
});
After that the separation would look smth like this:
var exceptionList = joinedFutureList.stream()
.filter(obj -> obj instanceof Exception)
.peek(System.out::println)
.collect(Collectors.toList());
var successList = joinedFutureList.stream()
.filter(obj -> obj instanceof String)
.peek(System.out::println)
.collect(Collectors.toList());
Existing code that I have:
private Flux<Integer> testGetFluxTestData() {
return Flux.just(new TestData(1), new TestData(2))
.collectList()
.map(list -> list.stream()
.map(TestData::getId)
.collect(Collectors.toList()))
.flatMapMany(Flux::fromIterable);
}
I want to enrich existing code and throw an exception when some not allowed data received, I made the following changes:
private Flux<Integer> testGetFluxTestData2() {
return Flux.just(new TestData(1), new TestData(2))
.collectList()
.map(list -> {
return !list.contains(new TestData(1)) ?
list.stream()
.map(TestData::getId)
.collect(Collectors.toList()) :
Flux.error(new IllegalTestDataException("illegal test data 1"));
})
.flatMapMany(Flux::fromIterable);
}
but my implementation even noncompilable due to the following line:
Flux.error(new IllegalTestDataException("illegal test data 1"));
Could you please suggest, how to handle exception throwing for my particular scenario?
You are attempting to map from a List<TestData> to either a List<Integer> or a Flux<?> (error), which makes the desired result type ambiguous. Returning a reactive type in a mapping function is generally not desired (you'd want to do that in a flatmapping function).
(side note: even if you were in a flatMap, it wouldn't work either because at that point you're in Mono API due to collectList, so Mono.flatMap expects a Mono result to the Function).
Note that the map operator catches exceptions from the lambda and turn them into an onError signal, so technically you could replace the Flux.error with a throw.
Otherwise, you'd need to turn the map into a flatMap and the Flux.error into a Mono.error, for the reasons stated above.
I am trying to iterate through two lists and I am getting an IllegalStateException: stream has already been operated upon or closed
file().forEach(line -> {
boolean match = ordersSupplier.get().anyMatch(order -> order == line.id);
if (match) {
//do something;
}
});
I know that anyMatch is a terminal operation, thats why I am getting this error. Can I parameterize a Supplier für anyMatch with the id of the line? Any Idea?
Collect the order ids in a Set and filter your line with the Stream API. Then you can execute your code in a forEach.
Set<String> orderIds = ordersSupplier.get().collect(toSet());
file().stream()
.filter(l -> orderIds.contains(l.id))
.forEach(
line -> {
//do something;
});
I have a very large list and want to perform a simple Filter operation on it in a background thread, and then get the list of results that match the filter criteria at the end of the operation.
I'm still new at RxJava so I'm struggling to get this to work. What I have is the following (largeList is an ArrayList filled with Item):
Observable
.fromIterable(largeList)
.filter { it.name.contains(query) }
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe { ... }
When I subscribe, I get back a single Item instead of a complete list of all items which passed the filter. How can I create a sublist of all matched results?
Use the toList() operator.
For a given Observable, it will collect incoming emissions into a List and then push that entire List as a single emission (through Single>).
Observable
.fromIterable(largeList)
.filter { it.name.contains(query) }
.toList()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe { ... }
Also, you can optionally specify an integer argument to serve as the capacityHint, and that will optimize the initialization of ArrayList to expect roughly that number of items:
Observable
.fromIterable(largeList)
.filter { it.name.contains(query) }
.toList(capacityHint)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe { ... }
Given I subscribe to 2 different Observables and I want get both of them on onNext after do some operations to them
let's say I have 2 Observables
Observable<List<String>> childName = Observable.from(children)... some operations
Observable<List<String>> teacherName = Observable.from(teachers)... some operations
how do I get both of them on my subscribe?
subscribe(
onNext(List<String> childName, List<String> className)
so that I can pass both of them in my listener in that manner.
I don't want to combine them, I just want when after the operations are finished, get both of them and pass it on my subscriptions
You can zip their values into a Pair:
Observable.zip(childName, className,
(a, b) -> Pair.of(a, b))
.subscribe((Pair<List<String>, List<String>> pair) -> {
// do something with pair.first and pair.second
}, Throwable::printStackTrace);
hacky but simple
Observable.zip(childName, teacherName, (childList, teachersList) -> {
// handle childList & teachersList
return Observable.empty();
}).subscribe(o -> {}, error -> {
//handle errors
});