I am new to RxJava but I am integrating it into a project that I am working on to help me learn it. I have run into a question about best practices.
I have a quest
As a Rx newbie, I was also searching for a simple answer to process exceptions separately and continue processing next event but could not find answers to what @Daniele Segato was asking. Here is one solution where you don't have control:
Above examples assume you have control over observables i.e., one way is to delay errors to the end using mergeDelayError OR return a known empty event Observable for every event as Observable separately using merge.
If it is a source event error, you can use lift to create another observable that basically processes the value of current Observable gracefully. SimpleErrorEmitter class simulates an unbounded stream that can fail sometimes.
Observable.create(new SimpleErrorEmitter())
// transform errors to write to error stream
.lift(new SuppressError<Integer>(System.err::println))
.doOnNext(System.out::println) // and everything else to console
.subscribe();
class SimpleErrorEmitter implements OnSubscribe<Integer> {
@Override
public void call(Subscriber<? super Integer> subscriber) {
subscriber.onNext(1);
subscriber.onNext(2);
subscriber.onError(new FooException());
subscriber.onNext(3);
subscriber.onNext(4);
subscriber.onCompleted();
}
class SuppressError<T> implements Operator<T, T> {
final Action1<Throwable> onError;
public SuppressError(Action1<Throwable> onError) {
this.onError = onError;
}
@Override
public Subscriber<? super T> call(Subscriber<? super T> t1) {
return new Subscriber<T>(t1) {
@Override
public void onNext(T t) {
t1.onNext(t);
}
@Override
public void onError(Throwable e) { // handle errors using a separate function
onError.call(e);
}
@Override
public void onCompleted() {
t1.onCompleted();
}
};
}
If it is a subscriber processing error which can try/catch and continue gracefully
Observable<Integer> justInts = justStrs.map((str) -> {
try {
return Integer.parseInt(str);
} catch (NumberFormatException e) {
return null;
}
});
I am still trying to find a simple way to just retry or delay try the failed event and continue from next.
Observable<String> justStrs = Observable
.just("1", "2", "three", "4", "5") // or an unbounded stream
// both these retrying from beginning
// when you delay or retry, if they are of known exception type
.retryWhen(ex -> ex.flatMap(eachex -> {
// for example, if it is a socket or timeout type of exception, try delaying it or retrying it
if (eachex instanceof RuntimeException) {
return Observable.timer(1L, TimeUnit.MICROSECONDS, Schedulers.immediate());
}
return Observable.error(eachex);
}))
// or simply retry 2 times
.retry(2) // if it is the source problem, attempt retry
.doOnError((ex) -> System.err.println("On Error:" + ex));
Reference: https://groups.google.com/forum/#!topic/rxjava/trm2n6S4FSc
The best practice is to use mergeDelayError( ) that combine multiple Observables into one, allowing error-free Observables to continue before propagating errors.
mergeDelayError
behaves much like merge
. The exception is when one of the Observables being merged terminates with an onError notification. If this happens with merge, the merged Observable will immediately issue an onError notification and terminate. mergeDelayError, on the other hand, will hold off on reporting the error until it has given any other non-error-producing Observables that it is merging a chance to finish emitting their items, and it will emit those itself, and will only terminate with an onError notification when all of the other merged Observables have finished.
Since you want to ignore the error, you can try onErrorResumeNext(Observable.<String>empty());
. For example,
Observable<String> info1 = getInfo("1::: ", t1, "2").onErrorResumeNext(Observable.<String>empty());
Observable<String> info2 = getInfo("2::: ", t1, "3").onErrorResumeNext(Observable.<String>empty());
return Observable.merge(info1, info2);
Looking at Observable.flatMap
's source:
return merge(map(func));
If you want all possible userids to be processed, you can go ahead with modified version of flatMap:
Observable.mergeDelayError(userIdObservable.map(userInfoFunc))
Further on, if you say:
If any of the network requests fails for the userid then that userid won't be updated and can be skipped
Then don't use:
return Observable.mergeDelayError(info1, info2);
Because this will cause both info1 and info2 to be requested even when one of them fails.
Rather go with:
return Observable.merge(info1, info2);
When info1 and info2 are subscribed to the same thread, they will run sequentially, so if info1 fails, info2 will never be requested. Since info1 and info2 are I/O bounded, I assume you want to run them in parallel:
getInfo("1::: ", t1, "2").subscribeOn(Schedulers.io());
getInfo("2::: ",t1, "3").subscribeOn(Schedulers.io());
This should significantly speed up your processing
The whole code:
public class TestMergeDelayError {
public static Observable<String> getUserIds() {
return Observable.from(new String[]{"1", "2", "3", "4", "5", "6"});
}
public static Observable<String> getInfo(final String prefix, final String integer, final String errorNumber) {
return Observable.create(new OnSubscribeFunc<String>() {
public Subscription onSubscribe(Observer<? super String> t1) {
if (integer.contains(errorNumber)) {
t1.onError(new Exception());
} else {
t1.onNext(prefix + integer);
t1.onCompleted();
}
return Subscriptions.empty();
}
})
.subscribeOn(Schedulers.io());
}
public static void main(String[] args) {
Observable<String> userIdObservable = getUserIds();
Observable<String> t = Observable.mergeDelayError(userIdObservable.map(new Func1<String, Observable<String>>() {
public Observable<String> call(final String t1) {
Observable<String> info1 = getInfo("1::: ", t1, "2");
Observable<String> info2 = getInfo("2::: ",t1, "3");
return Observable.merge(info1, info2);
}
}));
//rest is the same
}
}