Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RxJava - Using Single.Error / Observable.error vs throwing exception

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);
  }
}
like image 676
Joyson Avatar asked Jul 21 '26 11:07

Joyson


1 Answers

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.

like image 74
Hans Wurst Avatar answered Jul 23 '26 03:07

Hans Wurst