I have recently started using RxJava2 in one of my projects and currently I am working on implementing error handling in it.
I have written a mock class below in which I was initially throwing the error after wrapping it in a custom exception. However some of the examples I came across on error handling on stackoverflow and other sites used Single.error instead.
I used both approaches and they resulted in my subscribers onError method being invoked with the / by zero exception. I didn't notice any difference between the two.
There is comprehensive documentation on Error Handling and Error Handling Operators along with a lot of other articles on how to handle the exception after it is thrown. But the information in the javadoc for Single.error and Observable.error is quite minimal.
Is there an advantage of using Single.error or Observable.error over just throwing the exception? When do we choose one approach over the other?
public class Test {
public static void main(String[] args){
Single.just(1)
.flatMap(x -> externalMethod(x))
.subscribe(
s -> System.out.println("Success : " + s),
e -> System.out.println("Error : "+e)
);
}
public static Single<Integer> externalMethod(int x){
int result = 0;
try{
/* Some database / time consuming logic */
result = x % 0;
}
catch (Exception e){
throw new CustomException(e.getMessage()); // --> APPROACH 1
OR
return Single.error(new CustomException(e.getMessage())); // --> APPROACH 2
}
return Single.just(result);
}
}
Actually it does not matter, becaues RxJava tries to catch and relay all Throwables
APPROACH 1 -- throw new CustomException(); (io.reactivex.internal.operators.single.SingleFlatMap)
@Override
public void onSuccess(T value) {
SingleSource<? extends R> o;
try {
o = ObjectHelper.requireNonNull(mapper.apply(value), "The single returned by the mapper is null");
} catch (Throwable e) {
Exceptions.throwIfFatal(e);
downstream.onError(e);
return;
}
if (!isDisposed()) {
o.subscribe(new FlatMapSingleObserver<R>(this, downstream));
}
}
You see here, that given mapper from flatMap is invoked with an try-catch. If the mapper throws a Throwable, the Throwable will be forwarded via onError to downstream subscriber.
APPROACH 2 -- return Single.error(...) (io.reactivex.internal.operators.single.SingleError)
Single#error
@Override
protected void subscribeActual(SingleObserver<? super T> observer) {
Throwable error;
try {
error = ObjectHelper.requireNonNull(errorSupplier.call(), "Callable returned null throwable. Null values are generally not allowed in 2.x operators and sources.");
} catch (Throwable e) {
Exceptions.throwIfFatal(e);
error = e;
}
EmptyDisposable.error(error, observer);
}
public static void error(Throwable e, SingleObserver<?> observer) {
observer.onSubscribe(INSTANCE);
observer.onError(e);
}
Single#error emits given Throwable on subscription via #onError
When a value is emitted to Single#flatMap the mapper is applied and a subscription is opened returned value from the mapper.
(io.reactivex.internal.operators.single.SingleFlatMap.SingleFlatMapCallback.FlatMapSingleObserver)
@Override
public void onSubscribe(final Disposable d) {
DisposableHelper.replace(parent, d);
}
@Override
public void onError(final Throwable e) {
downstream.onError(e);
}
The returned Single returns a Single#error, which emits a Throwable via #onError. Given #onError will be delegated to the downstream subscriber via onError.
Performance wise one could be faster than the other, but this must be measured to have an exact image. Returning Single#error does more allocations and has more methods on the Stack (subscribeActual). On the other side, when throwing a Throwable it must be caught and handeled.
Therefore in my opinion it acutally does not matter, whether you use the one or the other.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With