Details and examples of error handling operation of Rxjava2 Observable

Keywords: Mobile Java github

Briefly:

Requirements understanding:

In Rxjava, when an exception occurs in the data processing and distribution, the observer will receive an Error notification. What if you don't want to send the exception notification and handle it yourself? Of course, the answer is yes. In Rxjava, many operators can be used to respond to the onError notification sent by Observable or recover from the Error.

For example:

  1. Swallow this error, switch to a standby Observable to continue transmitting data
  2. Swallow this error and launch the default
  3. Swallow the error and try to restart the Observable immediately
  4. Swallow this error and restart the Observable after some fallback intervals

The common error handling operators in Rxjava are as follows:

  • onErrorReturn(): instructs Observable to emit a specific data when encountering an error
  • onErrorResumeNext(): instructs Observable to launch a data sequence when encountering an error
  • onExceptionResumeNext(): indicates that the Observable continues to transmit data when encountering an error
  • retry(): indicates that the Observable will retry when it encounters an error
  • retryWhen(): indicates that when the Observable encounters an error, it will pass the error to another Observable to decide whether to re subscribe to the Observable

1. Catch

Recover the launch data from the onError notification.

The Catch operator intercepts the onError notification of the original Observable and replaces it with other data items or data sequences, so that the resulting Observable can terminate normally or not at all.

1.1 onErrorReturn

The onErrorReturn method returns a new Observable that mirrors the original Observable behavior. The latter ignores the onError call of the former and does not pass the error to the observer. As an alternative, it will send a special item and call the observer's onCompleted method.

  • onErrorReturnItem(T item): let Observable emit a specified item when it encounters an error and terminate normally.

  • Onerrorreturn (Function < throwable, t > valuesupplier): when the Observable encounters an error, it uses a Function to judge and return the specified type of data, and terminates normally.

Example code:

    // Create an Observable that can emit exceptions
    Observable<Integer> observable = Observable.create(new ObservableOnSubscribe<Integer>() {
        @Override
        public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
            emitter.onNext(1);
            emitter.onNext(2);
            emitter.onNext(1 / 0);  // Generate an exception
            emitter.onNext(3);
            emitter.onNext(4);
        }
    });

    /** 1. onErrorReturnItem(T item)
     * Let Observable emit a specified item and terminate normally when it encounters an error.
     */
    observable.onErrorReturnItem(888)   // Send the specified 888 data when the source Observable is abnormal
            .subscribe(new Observer<Integer>() {
                @Override
                public void onSubscribe(Disposable d) {
                    System.out.println("--> onSubscribe(1)");
                }

                @Override
                public void onNext(Integer integer) {
                    System.out.println("--> onNext(1): " + integer);
                }

                @Override
                public void onError(Throwable e) {
                    System.out.println("--> onError(1): " + e);
                }

                @Override
                public void onComplete() {
                    System.out.println("--> onCompleted(1)");
                }
            });

    System.out.println("-----------------------------------------------");
    /**
     * 2. onErrorReturn(Function<Throwable, T> valueSupplier)
     * When the Observable encounters an Error, it accepts the Error parameter through a Function, judges and returns the specified type of data, and terminates normally.
     */
    observable.onErrorReturn(new Function<Throwable, Integer>() {
        @Override
        public Integer apply(Throwable throwable) throws Exception {
            System.out.println("--> apply(1): e = " + throwable);
            return 888; // Send the specified 888 data when the source Observable is abnormal
        }
    }).subscribe(new Observer<Integer>() {
        @Override
        public void onSubscribe(Disposable d) {
            System.out.println("--> onSubscribe(2)");
        }

        @Override
        public void onNext(Integer integer) {
            System.out.println("--> onNext(2): " + integer);
        }

        @Override
        public void onError(Throwable e) {
            System.out.println("--> onError(2): " + e);
        }

        @Override
        public void onComplete() {
            System.out.println("--> onCompleted(2)");
        }
    });

Output:

--> onSubscribe(1)
--> onNext(1): 1
--> onNext(1): 2
--> onNext(1): 888
--> onCompleted(1)
-----------------------------------------------
--> onSubscribe(2)
--> onNext(2): 1
--> onNext(2): 2
--> apply(1): e = java.lang.ArithmeticException: / by zero
--> onNext(2): 888
--> onCompleted(2)

Javadoc: onErrorReturnItem(T item)
Javadoc: onErrorReturn(Function<Throwable, T> valueSupplier)

1.2 onErrorResumeNext

The onErrorResumeNext method returns a new Observable that mirrors the original Observable behavior. The latter ignores the onError call of the former and does not pass the error to the observer. As an alternative, it will start another specified standby Observable.

  • onErrorResumeNext(ObservableSource next): allows the Observable to start transmitting the second specified data sequence of the Observable when it encounters an error.
  • Onerrorresumenext (Function < throwable, observablesource < T > > resumefunction): allows Observable to accept the Error parameter through a Function when encountering an Error and judge to return the specified data sequence of the second Observable.

Example code:

    // Create an Observable that can emit exceptions
    Observable<Integer> observable = Observable.create(new ObservableOnSubscribe<Integer>() {
        @Override
        public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
            emitter.onNext(1);
            emitter.onNext(2);
            emitter.onNext(1 / 0);  // Generate an exception
            emitter.onNext(3);
            emitter.onNext(4);
        }
    });
    
    /**
     * 3. onErrorResumeNext(ObservableSource next)
     * Let Observable start transmitting the data sequence of the second specified Observable when encountering an error
     */
    observable.onErrorResumeNext(Observable.just(888))  // Continue to launch this Observable when an exception occurs
            .subscribe(new Observer<Integer>() {
                @Override
                public void onSubscribe(Disposable d) {
                    System.out.println("--> onSubscribe(3)");
                }

                @Override
                public void onNext(Integer integer) {
                    System.out.println("--> onNext(3): " + integer);
                }

                @Override
                public void onError(Throwable e) {
                    System.out.println("--> onError(3): " + e);
                }

                @Override
                public void onComplete() {
                    System.out.println("--> onCompleted(3)");
                }
            });

    System.out.println("-----------------------------------------------");
    /**
     * 4. onErrorResumeNext(Function<Throwable, ObservableSource<T>> resumeFunction)
     * Let the Observable accept the Error parameter through a Function when encountering an Error and judge to return the specified data sequence of the second Observable
     */
    observable.onErrorResumeNext(new Function<Throwable, ObservableSource<? extends Integer>>() {
        @Override
        public ObservableSource<? extends Integer> apply(Throwable throwable) throws Exception {
            System.out.println("--> apply(4): " + throwable);
            return Observable.just(888);    // Continue to launch this Observable when an exception occurs
        }
    }).subscribe(new Observer<Integer>() {
        @Override
        public void onSubscribe(Disposable d) {
            System.out.println("--> onSubscribe(4)");
        }

        @Override
        public void onNext(Integer integer) {
            System.out.println("--> onNext(4): " + integer);
        }

        @Override
        public void onError(Throwable e) {
            System.out.println("--> onError(4): " + e);
        }

        @Override
        public void onComplete() {
            System.out.println("--> onCompleted(4)");
        }
    });

Output:

--> onSubscribe(3)
--> onNext(3): 1
--> onNext(3): 2
--> onNext(3): 888
--> onCompleted(3)
-----------------------------------------------
--> onSubscribe(4)
--> onNext(4): 1
--> onNext(4): 2
--> apply(4): java.lang.ArithmeticException: / by zero
--> onNext(4): 888
--> onCompleted(4)

Javadoc: onErrorResumeNext(ObservableSource next)
Javadoc: onErrorResumeNext(Function<Throwable, ObservableSource<T>> resumeFunction)

1.3 onExceptionResumeNext

Similar to onErrorResumeNext, the onExceptionResumeNext method returns a new Observable that mirrors the original Observable behavior, and also uses a standby Observable. The difference is that if the Throwable received by onError is not an Exception, it will pass the error to the observer's onError method instead of using the standby Observable.

Resolution: onExceptionResumeNext will only handle exceptions of type Exception. If the Throwable received by onError is not an Exception, it will pass the error to the observer's onError method and will not use the standby Observable.

Example code:

    // Create an Observable that can emit exceptions
    Observable<Integer> observable1 = Observable.create(new ObservableOnSubscribe<Integer>() {
        @Override
        public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
            emitter.onNext(1);
            emitter.onNext(2);
        //  Emitter.onerror (New throwable ("this is throwable!"); / / throwable type exception, notify the observer directly
        //  Emitter.onerror (new error ("this is error!"); / / if the error type is abnormal, notify the observer directly
            emitter.onError(new Exception("This is Exception!"));  // Exception type exception, handle it and send standby Observable data
        //    emitter.onNext(1 / 0); / / an ArithmeticException exception will be generated. The exception will be processed and the standby Observable data will be sent
            emitter.onNext(3);
            emitter.onNext(4);
        }
    });
    /**
     * 5. onExceptionResumeNext(ObservableSource next)
     *  If the Throwable received by onError is not an Exception, it will pass the error to the observer's onError method and will not use the standby Observable
     *  Standby Observable processing only for Exception notifications of type Exception
     */
    observable1.onExceptionResumeNext(Observable.just(888))
            .subscribe(new Observer<Integer>() {
                @Override
                public void onSubscribe(Disposable d) {
                    System.out.println("--> onSubscribe(5)");
                }

                @Override
                public void onNext(Integer integer) {
                    System.out.println("--> onNext(5): " + integer);
                }

                @Override
                public void onError(Throwable e) {
                    System.out.println("--> onError(5): " + e);
                }

                @Override
                public void onComplete() {
                    System.out.println("--> onCompleted(5)");
                }
            });

Output:

--> onSubscribe(5)
--> onNext(5): 1
--> onNext(5): 2
--> onNext(5): 888
--> onCompleted(5)

Javadoc: onExceptionResumeNext(ObservableSource next)

2. Retry

If the original Observable encounters an error, subscribing to it again expects it to terminate normally.

The retry operator will not pass the onError notification of the original Observable to the observer. It will subscribe to the Observable and give it the opportunity to complete its data sequence without error. Retry always delivers the onNext notification to the observer, which may cause duplicate data items due to re subscription.

2.1 retry()

retry(): no matter how many times the onError notification is received, the parameter free version of retry will continue to subscribe and launch the original Observable.

Note: if an exception is encountered, the original Observable will be re subscribed unconditionally until all data sequences are transmitted without exception. So if your subscription will not return to normal after the exception occurs, you will continue to subscribe, and there is a risk of memory leakage.

2.2 retry(long times)

retry(long times): a retry that accepts a single count parameter will re subscribe to the specified number of times at most. If the number exceeds, it will not attempt to re subscribe. It will pass the latest onError notification to its observer.

2.3 retry(long times, Predicate predicate)

retry (long times, predict < throwable > predict): after encountering an exception, you can re subscribe for times at most. Each time you re subscribe, the function predict finally determines whether to continue to re subscribe. If times reaches the upper limit or any of the first conditions in false returned by predict are met, the re subscription will be terminated. retry will pass the latest onError notification to it The observer of.

2.4 retry(Predicate predicate)

Retry (predict < Throwable > predict): accepts a predicate function as a parameter. The two parameters of this function are: the number of retries and the Throwable that causes the onError notification to be sent. This function returns a Boolean value. If it returns true, retry should subscribe to and mirror the original Observable again. If it returns false, retry will pass the latest onError notification to its observer

2.5 retry(BiPredicate predicate)

retry (bipredicte < Integer, throwable > predict): when encountering an exception, use the function predict to determine whether to re subscribe to Observable, and pass the parameter Integer to predict the number of times to re subscribe. retry will pass the latest onError notification to its observer.

2.6 retryUntil(BooleanSupplier stop)

Retry until (Boolean supplier stop): retry the re subscription until the given stop function returns true, and retry will pass the latest onError notification to its observer.

2.7 retryWhen(Function handler)

retryWhen (function < Observable < Throwable >, observablesource > handler): retryWhen is similar to retry. The difference is that retryWhen passes Throwable in onError to a function, which generates another Observable. After observing its result, retryWhen decides whether to re subscribe to the original Observable or not. If the Observable sends a data, it will subscribe again. If the Observable sends an onError notification, it will pass the notification to the observer and terminate.

Instance code:

	// flag for emitted onError times
    public static int temp = 0;

    // Create an Observable that can send Error notifications
    Observable<Integer> observable = Observable.create(new ObservableOnSubscribe<Integer>() {
        @Override
        public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
            emitter.onNext(1);
            emitter.onNext(2);
            if (temp <= 2) {
                emitter.onError(new Exception("Test Error!"));
                temp++;
            }
            emitter.onNext(3);
            emitter.onNext(4);
        }
    });

    /**
     * 1. retry()
     *  No matter how many times you receive the onError notification, you will continue to subscribe and launch the original Observable.
     */
    observable.doOnSubscribe(new Consumer<Disposable>() {
        @Override
        public void accept(Disposable disposable) throws Exception {
            System.out.println("----> doOnSubscribe(1)");
        }
    }).retry().subscribe(new Consumer<Integer>() {
        @Override
        public void accept(Integer integer) throws Exception {
            System.out.println("--> accept(1): " + integer);
        }
    });

    System.out.println("---------------------------------------------");
    temp = 0;
    /**
     * 2. retry(long times)
     *  After encountering an exception, the maximum number of times to re subscribe to the source Observable times is
     */
    observable.doOnSubscribe(new Consumer<Disposable>() {
        @Override
        public void accept(Disposable disposable) throws Exception {
            System.out.println("----> doOnSubscribe(2)");
        }
    }).retry(1) // 1 time of repeated subscription after encountering exception
      .subscribe(new Observer<Integer>() {
            @Override
            public void onSubscribe(Disposable d) {
                System.out.println("--> onSubscribe(2)");
            }

            @Override
            public void onNext(Integer integer) {
                System.out.println("--> onNext(2): " + integer);
            }

            @Override
            public void onError(Throwable e) {
                System.out.println("--> onError(2): " + e);
            }

            @Override
            public void onComplete() {
                System.out.println("--> onCompleted(2)");
            }
      });

    System.out.println("---------------------------------------------");
    temp = 0;
    /**
     * 3. retry(long times, Predicate<Throwable> predicate)
     *  After encountering an exception, you can re subscribe to times at most. After each re subscription, the function predict finally determines whether to continue to re subscribe
     *  If times reaches the upper limit or predicate returns false, any of the conditions that are met first will terminate the re subscription
     */
    observable.doOnSubscribe(new Consumer<Disposable>() {
        @Override
        public void accept(Disposable disposable) throws Exception {
            System.out.println("----> doOnSubscribe(3)");
        }
    }).retry(2, new Predicate<Throwable>() {
        @Override
        public boolean test(Throwable throwable) throws Exception {
            System.out.println("--> test(3)");
            if(throwable instanceof Exception) {
                return true;    // Continue subscription after exception notification
            }
            return false;
        }
    }).subscribe(new Observer<Integer>() {
        @Override
        public void onSubscribe(Disposable d) {
            System.out.println("--> onSubscribe(3)");
        }

        @Override
        public void onNext(Integer integer) {
            System.out.println("--> onNext(3): " + integer);
        }

        @Override
        public void onError(Throwable e) {
            System.out.println("--> onError(3): " + e);
        }

        @Override
        public void onComplete() {
            System.out.println("--> onCompleted(3)");
        }
    });

    System.out.println("---------------------------------------------");
    temp = 0;
    /**
     * 4. retry(Predicate<Throwable> predicate)
     *  When an exception is encountered, the function predicate is used to determine whether to re subscribe to the Observable feed
     */
    observable.doOnSubscribe(new Consumer<Disposable>() {
        @Override
        public void accept(Disposable disposable) throws Exception {
            System.out.println("----> doOnSubscribe(4)");
        }
    }).retry(new Predicate<Throwable>() {
        @Override
        public boolean test(Throwable throwable) throws Exception {
            if (throwable instanceof Exception) {
                return true;    // Continue subscription after exception notification
            }
            return false;
        }
    }).subscribe(new Observer<Integer>() {
        @Override
        public void onSubscribe(Disposable d) {
            System.out.println("--> onSubscribe(4)");
        }

        @Override
        public void onNext(Integer integer) {
            System.out.println("--> onNext(4): " + integer);
        }

        @Override
        public void onError(Throwable e) {
            System.out.println("--> onError(4): " + e);
        }

        @Override
        public void onComplete() {
            System.out.println("--> onCompleted(4)");
        }
    });

    System.out.println("---------------------------------------------");
    temp = 0;
    /**
     * 5. retry(BiPredicate<Integer, Throwable> predicate)
     *   When an exception is encountered, the function predict is used to determine whether to re subscribe to Observable, and the number of times the parameter integer is passed to predict for re subscription
     */
    observable.doOnSubscribe(new Consumer<Disposable>() {
        @Override
        public void accept(Disposable disposable) throws Exception {
            System.out.println("----> doOnSubscribe(5)");
        }
    }).retry(new BiPredicate<Integer, Throwable>() {
        @Override
        public boolean test(Integer integer, Throwable throwable) throws Exception {
            System.out.println("--> test(5): " + integer);
            if (throwable instanceof Exception) {
                return true;    // Continue subscription after exception notification
            }
            return false;
        }
    }).subscribe(new Observer<Integer>() {
        @Override
        public void onSubscribe(Disposable d) {
            System.out.println("--> onSubscribe(5)");
        }

        @Override
        public void onNext(Integer integer) {
            System.out.println("--> onNext(5): " + integer);
        }

        @Override
        public void onError(Throwable e) {
            System.out.println("--> onError(5): " + e);
        }

        @Override
        public void onComplete() {
            System.out.println("--> onCompleted(5)");
        }
    });

    System.out.println("---------------------------------------------");
    temp = 0;
    /**
     * 6. retryUntil(BooleanSupplier stop)
     * Retry subscribing again until the given stop function stop returns true
     */
    observable.doOnSubscribe(new Consumer<Disposable>() {
        @Override
        public void accept(Disposable disposable) throws Exception {
            System.out.println("----> doOnSubscribe(6)");
        }
    }).retryUntil(new BooleanSupplier() {
        @Override
        public boolean getAsBoolean() throws Exception {
            System.out.println("--> getAsBoolean(6)");
            if(temp == 1){  // Conditions met, stop re subscription
                return true;
            }
            return false;
        }
    }).subscribe(new Observer<Integer>() {
        @Override
        public void onSubscribe(Disposable d) {
            System.out.println("--> onSubscribe(6)");
        }

        @Override
        public void onNext(Integer integer) {
            System.out.println("--> onNext(6): " + integer);
        }

        @Override
        public void onError(Throwable e) {
            System.out.println("--> onError(6): " + e);
        }

        @Override
        public void onComplete() {
            System.out.println("--> onCompleted(6)");
        }
    });


    System.out.println("---------------------------------------------");
    temp = 0;
    /**
     * 7. retryWhen(Function<Observable<Throwable>, ObservableSource> handler)
     *  Pass Throwable in onError to a function handler, which generates another Observable,
     *  retryWhen Observe its results and decide whether to subscribe to the original Observable again.
     *  If the Observable transmits a piece of data, it will subscribe again,
     *  If the Observable sends an onError notification, it passes the notification to the observer and terminates.
     */
    observable.doOnSubscribe(new Consumer<Disposable>() {
        @Override
        public void accept(Disposable disposable) throws Exception {
            System.out.println("----> doOnSubscribe(7)");
        }
    }).retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {
        @Override
        public ObservableSource<?> apply(Observable<Throwable> throwableObservable) throws Exception {
            System.out.println("--> apply(7)");
            // The subscription is based on whether the Observable of the generated Error transmits data normally. If the Error notification is transmitted, it will be directly delivered to the observer and terminated
            return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {
                @Override
                public ObservableSource<?> apply(Throwable throwable) throws Exception {
                    if (temp == 1) {
                        return Observable.error(throwable); // When the conditions are met, pass the Error and terminate the re subscription
                    }
                    return Observable.timer(1, TimeUnit.MILLISECONDS);  // Normal launch data, can be re subscribed
                }
            });
        }
    }).subscribe(new Observer<Integer>() {
        @Override
        public void onSubscribe(Disposable d) {
            System.out.println("--> onSubscribe(7)");
        }

        @Override
        public void onNext(Integer integer) {
            System.out.println("--> onNext(7): " + integer);
        }

        @Override
        public void onError(Throwable e) {
            System.out.println("--> onError(7): " + e);
        }

        @Override
        public void onComplete() {
            System.out.println("--> onCompleted(7)");
        }
    });

    System.in.read();

Output:

----> doOnSubscribe(1)
--> accept(1): 1
--> accept(1): 2
----> doOnSubscribe(1)
--> accept(1): 1
--> accept(1): 2
----> doOnSubscribe(1)
--> accept(1): 1
--> accept(1): 2
----> doOnSubscribe(1)
--> accept(1): 1
--> accept(1): 2
--> accept(1): 3
--> accept(1): 4
---------------------------------------------
--> onSubscribe(2)
----> doOnSubscribe(2)
--> onNext(2): 1
--> onNext(2): 2
----> doOnSubscribe(2)
--> onNext(2): 1
--> onNext(2): 2
--> onError(2): java.lang.Exception: Test Error!
---------------------------------------------
--> onSubscribe(3)
----> doOnSubscribe(3)
--> onNext(3): 1
--> onNext(3): 2
--> test(3)
----> doOnSubscribe(3)
--> onNext(3): 1
--> onNext(3): 2
--> test(3)
----> doOnSubscribe(3)
--> onNext(3): 1
--> onNext(3): 2
--> onError(3): java.lang.Exception: Test Error!
---------------------------------------------
--> onSubscribe(4)
----> doOnSubscribe(4)
--> onNext(4): 1
--> onNext(4): 2
----> doOnSubscribe(4)
--> onNext(4): 1
--> onNext(4): 2
----> doOnSubscribe(4)
--> onNext(4): 1
--> onNext(4): 2
----> doOnSubscribe(4)
--> onNext(4): 1
--> onNext(4): 2
--> onNext(4): 3
--> onNext(4): 4
---------------------------------------------
--> onSubscribe(5)
----> doOnSubscribe(5)
--> onNext(5): 1
--> onNext(5): 2
--> test(5): 1
----> doOnSubscribe(5)
--> onNext(5): 1
--> onNext(5): 2
--> test(5): 2
----> doOnSubscribe(5)
--> onNext(5): 1
--> onNext(5): 2
--> test(5): 3
----> doOnSubscribe(5)
--> onNext(5): 1
--> onNext(5): 2
--> onNext(5): 3
--> onNext(5): 4
---------------------------------------------
--> onSubscribe(6)
----> doOnSubscribe(6)
--> onNext(6): 1
--> onNext(6): 2
--> getAsBoolean(6)
----> doOnSubscribe(6)
--> onNext(6): 1
--> onNext(6): 2
--> getAsBoolean(6)
--> onError(6): java.lang.Exception: Test Error!
---------------------------------------------
--> apply(7)
--> onSubscribe(7)
----> doOnSubscribe(7)
--> onNext(7): 1
--> onNext(7): 2
----> doOnSubscribe(7)
--> onNext(7): 1
--> onNext(7): 2
--> onError(7): java.lang.Exception: Test Error!

Javadoc: retry()
Javadoc: retry(long times)
Javadoc: retry(long times, Predicate<Throwable> predicate)
Javadoc: retry(Predicate<Throwable> predicate)
Javadoc: retry(BiPredicate<Integer, Throwable> predicate)
Javadoc: retryUntil(BooleanSupplier stop)
Javadoc: retryWhen(Function<Observable<Throwable>, ObservableSource> handler)

Summary

This section mainly introduces the handling of Error notification in Rxjava, which is mainly to unsubscribe the original Observable unconditionally or with specified conditions until there is no exception (normal transmission of all data sequences) or the specified conditions are met, and then terminate the subscription, and send the exception notification to the observer.

Tip: Rxjava2 version used above: 2.2.12

Rx introduction and explanation and complete catalog reference: Rxjava2 introduction and detailed examples

Instance code:

Posted by vinaip on Fri, 03 Jan 2020 02:15:43 -0800