No option for ConcatMap with skip error - RxJava - java

Consider this example:
I have a file downloading in sequence. If one download fails, it should move to next.
Psudo code:
Observable.from(urls)
.concatMap(url -> downloadObservable(url))
There is no option for moving to next url if the download fails.
There is no way to skip with onErrorResumeNext() as I just want to move to next url. Can anyone help?

There is an operator for this: concatMapDelayError since 1.3. In general, if there is a reason errors could be delayed until all sources have been consumed fully, there is likely a opNameDelayError operator for it.
Observable.from(urls)
.concatMapDelayError(url -> downloadObservable(url))
.doOnError(error -> {
if (error instanceof CompositeException) {
System.out.println(((CompositeException)error).getExceptions().size());
} else {
System.out.println(1);
}
});
(The doOnError addendum comes from the updated OP's cross post on the RxJava issue list.)

If you are using RxJava 1, a quick and dirty solution is to return null when the download fails and then filter them out:
Observable
.from(urls)
.concatMap(url -> downloadObservable(url).onErrorReturn(null))
.filter(result -> result != null)
A nicer solution would be to create a wrapper for the result having a method like wasSuccessful() for checking in the filter and a method like getResult() for extracting the result from the wrapper. This way you don't have to handle nulls.

According to: https://github.com/ReactiveX/RxJava/issues/3870 there is no way to do this. Of course you can introduce some other error handling, i.e. handle error inside downloadObservable then filter null answers.

You have to think that is a pipeline so, in case you don't want to stop the emission of the pipeline, you have to control the error and return something in order to continue with the next emission.
The only way to use onErrorResumeNext and not stop the emission after that, is if it´s executed in a flatMap
Observable.from(urls)
.flatMap(url -> downloadObservable(url)
.onErrorResumeNext(t -> Observable.just("Something went wrong"))))
You can see an example here https://github.com/politrons/reactive/blob/master/src/test/java/rx/observables/errors/ObservableExceptions.java

Related

How to pass Mono<> result from previous step to the next doOnSuccess() method

Let's say that I have a method addVoteToSong like:
public Mono<Map<Song, VoteKind>> addVoteToSong(Principal principal, String songId, VoteKind voteKind) {
return
userRepository.findUserByUsername(principal.getName())
.doOnSuccess(song -> songRepository.findSongById(songId))
.doOnSuccess(vote -> voteRepository.add(Vote.builder().song()))
.//(the rest of the code)
}
I want to pass a result from the line:
userRepository.findUserByUsername(principal.getName())
and
.doOnSuccess(song -> songRepository.findSongById(songId))
to the built object in the line:
.doOnSuccess(vote -> voteRepository.add(Vote.builder().song(here result from findSongById).user(here result from findUserByUsername))
Here comes the question, is it possible to reuse previous API call result in the next doOnSuccess method or I should split find API calls at the same time, giving up on Reactor's cascading operations? On the internet, I have found examples with single save method without basing on the indirect result of the reactive stream and that's why question occurred. I will be grateful for suggestions on how to reach a goal.
Martin,
First of all, be aware that .doOnXXX are just callbacks that will be executed on some archived conditions. You should avoid putting a business logic inside of them.
Coming back to the question, the first idea that comes to my mind is to benefit from zip operator. So you have to put 2 publishers .findUserByUsername and .findSongById and combine the result using BiFunction. So you can try the following:
public Mono<Map<Song, VoteKind>> addVoteToSong(Principal principal, String songId, VoteKind voteKind) {
return Mono
.zip(
userRepository.findUserByUsername(principal.getName()),
songRepository.findSongById(songId),
(user, song) -> voteRepository.add(Vote.builder().song(song).user(user).build())
)
.flatMap(Function.identity())
// your code is here
}

Sync two asynchronous API call with RxJava

In what way can we sync two asynchronous calls using RxJava? In the example below, the method contentService.listContents which is a API call must first finish before the processSchema method to take place for each schema.
schemaService.listSchema()
.toObservable()
.flatMapIterable(schemas -> {
schemas.forEach(schema -> {
// async call
contentService.listContents(schema.getName()).subscribe(contents -> {
doSomethingWithThe(contents);
});
});
// contentService.listContents` must complete first before
// processSchema should be called for each schema
return schemas;
}).subscribe(schema -> { processSchema(schema); },
error -> { Console.error(error.getMessage()); });
The problem with the code above the processSchema would not wait for the contentService.listContents since it is async not not synchronized with each other.
You have to use flatMap to process the schemas and since it is a list, you have to unroll it and flatMap again:
schemaService.listSchema()
.toObservable()
.flatMap(schemas ->
Observable.fromIterable(schemas)
.flatMap(schema ->
contentService.listContents(schema.getName())
.doOnNext(contents -> doSomethingWith(contents))
)
// probably you don't care about the inner contents
.ignoreElements()
// andThen will switch to this only when the sequence above completes
.andThen(Observable.just(schemas))
)
.subscribe(
schema -> processSchema(schema),
error -> Console.error(error.getMessage())
);
Note that you haven't defined the return types of the service calls so you may have to use flatMapSingle and doOnSuccess for example.
You are probably looking for flatMap.
From the docs
Continuations
Sometimes, when an item has become available, one would
like to perform some dependent computations on it. This is sometimes
called continuations and, depending on what should happen and what
types are involved, may involve various operators to accomplish.
Dependent
The most typical scenario is to given a value, invoke
another service, await and continue with its result:
service.apiCall()
.flatMap(value -> service.anotherApiCall(value))
.flatMap(next -> service.finalCall(next))
It is often the case also that later sequences would require values
from earlier mappings. This can be achieved by moving the outer
flatMap into the inner parts of the previous flatMap for example:
service.apiCall()
.flatMap(value ->
service.anotherApiCall(value)
.flatMap(next -> service.finalCallBoth(value, next))
)

Using ComputeIfAbsent in HashMap

I have read similar posts on this, but is this a right way to use computeIfAbsent function? cookieMap is a HashMap and responses is an Object which contains all the headers, cookies, responses, status Code etc...
cookieMap.computeIfAbsent("Varlink", varLink -> {
if (responses.getCookie("VARLINK").length() < 1) {
throw new ProviderException("Varlink not present in response, check response status!!!");
}
return responses.getCookie("VARLINK");
});
I will need to add multiple keys like this to the cookieMap. My initial thought was to put everything inside an If condition, but due to certain restrictions we are not supposed to have nested if-else conditions (I guess the Code Reviewer took the book Clean Code too seriously)
If responses and cookieMap are two different sources of data, then your snippet is correct. The only concern is calling cookieMap::getCookie twice which might be resolved using a variable as someone has suggested in the comments.
I'd shorten the entire expression using Optional to:
cookieMap.computeIfAbsent("Varlink", v -> {
Optional.of(respones.getCookie("VARLINK")) // Gets a cookie
.filter(c -> c.length() >= 1) // Filters the length
.orElseThrow(() -> new ProviderException("...")); // Returns only if present
});

How do I use RxJava 2 to orchestrate a race with retry?

Suppose we have a set of flaky (sometimes failing) parsers that may or may not be able to handle a given file i.e. a given parser either succeeds with some probability p > 0, or fails always (p=0). Is it possible to use RxJava to have this set of parsers subscribe to a stream of incoming files and 'race' to parse the file?
Given that it is possible for the parser to fail initially but still be able to parse the file, it is necessary to have them retry with some backoff policy. Given that it is also possible for no parser to be able to handle a given file, the retry count should be capped.
Implementing exponential backoff is relatively easy to implement using retryWhen with something like this (source):
source.retryWhen(errors ->
errors.zipWith(Observable.range(1, 3), (n, i) -> i)
.flatMap(retryCount -> Observable.timer((long) Math.pow(5, retryCount), TimeUnit.SECONDS))
);
However, setting up a parallel race is something I cannot figure out how to do. It seems like the amb operator is what we want here, but applying it to an arbitrary number of streams seems to require using blockingIterable, which (I think) defeats the purpose of the race as it blocks. I have been unable to find anything useful relating to this use case of amb on the internet.
My attempts thus far resemble something like this:
Set<Parser> parserSet = new HashSet<>();
parserSet.add(new Parser(..., ..., ...));
// Add more parsers
int numParsers = parserSet.size();
Flowable<Parser> parsers = Flowable.fromIterable(parserSet).repeat();
fileSource
.flatMap(f -> parsers.take(numParsers)
.map(p -> p.parse(f))
.retryWhen(/* snippet from above */)
.onErrorReturn(/* some error value */)
).take(1)
Flowable introduced the .parallel() operator which just recently got the addition of ParallelFailureHandling (see this pr) which has a RETRY method, but I can't seem to get the flowables to stop retrying after one of them has returned.
Is this problem solvable with RxJava?
Making the reasonable assumption that your parsers are synchronous, something like
Set<Parser> parserSet = new HashSet<>();
parserSet.add(new Parser(..., ..., ...));
// Add more parsers
int numParsers = parserSet.size();
ArrayList<Flowable<T>> parserObservableList = new ArrayList<>();
for (Parser p: parserSet) {
parserObservableList.add(Flowable.fromCallable(() -> p.parse(f))
.retryWhen(/* Add your retry logic */)
.onErrorReturn(/* some error value */));
}
Flowable.amb(parserObservableList).subscribe(/* do what you want with the results */);
should meet your requirements.

Rxjava 2 Maybe.toSingle error

why maybe.toSingle() throw error no such element? I tried to handle doOnError but doesn't work!!
Single<Integer> singleOdd = Single.just(1);
Single<Integer> singleEven = Single.just(2);
Single.concat(singleOdd.filter(integer -> integer%2 ==0).toSingle(),singleEven).doOnError(throwable -> throwable.printStackTrace()).subscribe();
why maybe.toSingle() throw error no such element?
filter() on a Single has two outcomes, either it passes and you have one item, or it doesn't pass and you have an empty Maybe. Converting back to Single mandates that you have exactly one item or an error.
I tried to handle doOnError but doesn't work!!
doOnError is not error handling from the stream's perspective but a peek into the error channel. You have to use onErrorResumeNext or retry to react to an error case.

Categories

Resources