1. Вступление
В этой статье мы рассмотрим, как обрабатывать исключения и ошибки с помощью RxJava.
Во-первых, имейте в виду, что Observable обычно не генерирует исключения. Вместо этого по умолчанию Observable вызывает метод onOrror () своего Observer, уведомляя наблюдателя о том, что только что произошла неисправимая ошибка, а затем завершает работу, не вызывая больше своих методов Observer’s .
-
Операторы обработки ошибок, которые мы собираемся представить, изменяют поведение по умолчанию, возобновляя или повторяя последовательность Observable . **
2. Зависимости Maven
Во-первых, давайте добавим RxJava в pom.xml :
<dependency>
<groupId>io.reactivex.rxjava2</groupId>
<artifactId>rxjava</artifactId>
<version>2.1.3</version>
</dependency>
Последнюю версию артефакта можно найти Вот .
3. Обработка ошибок
Когда возникает ошибка, нам обычно нужно как-то ее обработать. Например, измените связанные внешние состояния, возобновив последовательность с результатами по умолчанию, или просто оставьте так, чтобы ошибка могла распространяться.
3.1. Действие при ошибке
С помощью doOnError мы можем вызвать любое действие, которое необходимо, когда есть ошибка:
@Test
public void whenChangeStateOnError__thenErrorThrown() {
TestObserver testObserver = new TestObserver();
AtomicBoolean state = new AtomicBoolean(false);
Observable
.error(UNKNOWN__ERROR)
.doOnError(throwable -> state.set(true))
.subscribe(testObserver);
testObserver.assertError(UNKNOWN__ERROR);
testObserver.assertNotComplete();
testObserver.assertNoValues();
assertTrue("state should be changed", state.get());
}
В случае возникновения исключения при выполнении действия RxJava помещает исключение в CompositeException :
@Test
public void whenExceptionOccurOnError__thenCompositeExceptionThrown() {
TestObserver testObserver = new TestObserver();
Observable
.error(UNKNOWN__ERROR)
.doOnError(throwable -> {
throw new RuntimeException("unexcepted");
})
.subscribe(testObserver);
testObserver.assertError(CompositeException.class);
testObserver.assertNotComplete();
testObserver.assertNoValues();
}
3.2. Возобновить с элементами по умолчанию
Хотя мы можем вызывать действия с doOnError , но ошибка по-прежнему нарушает стандартный поток последовательности. Иногда мы хотим возобновить последовательность с параметром по умолчанию, что делает onErrorReturnItem :
@Test
public void whenHandleOnErrorResumeItem__thenResumed(){
TestObserver testObserver = new TestObserver();
Observable
.error(UNKNOWN__ERROR)
.onErrorReturnItem("singleValue")
.subscribe(testObserver);
testObserver.assertNoErrors();
testObserver.assertComplete();
testObserver.assertValueCount(1);
testObserver.assertValue("singleValue");
}
Если предпочтительным является динамический поставщик элементов по умолчанию, мы можем использовать onErrorReturn :
@Test
public void whenHandleOnErrorReturn__thenResumed() {
TestObserver testObserver = new TestObserver();
Observable
.error(UNKNOWN__ERROR)
.onErrorReturn(Throwable::getMessage)
.subscribe(testObserver);
testObserver.assertNoErrors();
testObserver.assertComplete();
testObserver.assertValueCount(1);
testObserver.assertValue("unknown error");
}
3.3. Возобновить с другой последовательностью
Вместо возврата к одному элементу мы можем предоставить запасную последовательность данных, используя onErrorResumeNext при обнаружении ошибок. Это поможет предотвратить распространение ошибок:
@Test
public void whenHandleOnErrorResume__thenResumed() {
TestObserver testObserver = new TestObserver();
Observable
.error(UNKNOWN__ERROR)
.onErrorResumeNext(Observable.just("one", "two"))
.subscribe(testObserver);
testObserver.assertNoErrors();
testObserver.assertComplete();
testObserver.assertValueCount(2);
testObserver.assertValues("one", "two");
}
Если резервная последовательность отличается в зависимости от конкретных типов исключений или если последовательность должна быть сгенерирована функцией, мы можем передать функцию в onErrorResumeNext:
@Test
public void whenHandleOnErrorResumeFunc__thenResumed() {
TestObserver testObserver = new TestObserver();
Observable
.error(UNKNOWN__ERROR)
.onErrorResumeNext(throwable -> Observable
.just(throwable.getMessage(), "nextValue"))
.subscribe(testObserver);
testObserver.assertNoErrors();
testObserver.assertComplete();
testObserver.assertValueCount(2);
testObserver.assertValues("unknown error", "nextValue");
}
3.4. Обрабатывать исключение Только
RxJava также предоставляет резервный метод, который позволяет продолжить последовательность с предоставленным Observable при возникновении исключения (но без ошибки):
@Test
public void whenHandleOnException__thenResumed() {
TestObserver testObserver = new TestObserver();
Observable
.error(UNKNOWN__EXCEPTION)
.onExceptionResumeNext(Observable.just("exceptionResumed"))
.subscribe(testObserver);
testObserver.assertNoErrors();
testObserver.assertComplete();
testObserver.assertValueCount(1);
testObserver.assertValue("exceptionResumed");
}
@Test
public void whenHandleOnException__thenNotResumed() {
TestObserver testObserver = new TestObserver();
Observable
.error(UNKNOWN__ERROR)
.onExceptionResumeNext(Observable.just("exceptionResumed"))
.subscribe(testObserver);
testObserver.assertError(UNKNOWN__ERROR);
testObserver.assertNotComplete();
}
Как показывает приведенный выше код, при возникновении ошибки onExceptionResumeNext не запускается, чтобы возобновить последовательность.
4. Повторить при ошибке
Нормальная последовательность может быть нарушена из-за временного сбоя системы или ошибки сервера. В этих ситуациях мы хотим повторить попытку и подождать, пока последовательность не будет исправлена.
К счастью, RxJava дает нам возможность выполнить именно это.
4.1. Повторить
С помощью http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Observable.html#retry-- _, Observable_ будет повторно подписываться бесконечное количество раз до тех пор, пока не возникнет ошибка. Но большую часть времени мы бы предпочли фиксированное количество повторов:
@Test
public void whenRetryOnError__thenRetryConfirmed() {
TestObserver testObserver = new TestObserver();
AtomicInteger atomicCounter = new AtomicInteger(0);
Observable
.error(() -> {
atomicCounter.incrementAndGet();
return UNKNOWN__ERROR;
})
.retry(1)
.subscribe(testObserver);
testObserver.assertError(UNKNOWN__ERROR);
testObserver.assertNotComplete();
testObserver.assertNoValues();
assertTrue("should try twice", atomicCounter.get() == 2);
}
4.2. Повторить при условии
Условная повторная попытка также возможна в RxJava, используя retry с предикатами или используя retryUntil :
@Test
public void whenRetryConditionallyOnError__thenRetryConfirmed() {
TestObserver testObserver = new TestObserver();
AtomicInteger atomicCounter = new AtomicInteger(0);
Observable
.error(() -> {
atomicCounter.incrementAndGet();
return UNKNOWN__ERROR;
})
.retry((integer, throwable) -> integer < 4)
.subscribe(testObserver);
testObserver.assertError(UNKNOWN__ERROR);
testObserver.assertNotComplete();
testObserver.assertNoValues();
assertTrue("should call 4 times", atomicCounter.get() == 4);
}
@Test
public void whenRetryUntilOnError__thenRetryConfirmed() {
TestObserver testObserver = new TestObserver();
AtomicInteger atomicCounter = new AtomicInteger(0);
Observable
.error(UNKNOWN__ERROR)
.retryUntil(() -> atomicCounter.incrementAndGet() > 3)
.subscribe(testObserver);
testObserver.assertError(UNKNOWN__ERROR);
testObserver.assertNotComplete();
testObserver.assertNoValues();
assertTrue("should call 4 times", atomicCounter.get() == 4);
}
4.3. RetryWhen
Помимо этих базовых опций, существует также интересный метод повтора:
retryWhen .
Это возвращает Observable, скажем, «NewO», который излучает те же значения, что и источник ObservableSource , скажем «OldO», но если возвращенное Observable «NewO» вызывает onComplete или onError , будет вызываться onComplete или onError__ подписчика.
И если «NewO» испускает какой-либо элемент, будет инициирована повторная подписка на источник ObservableSource «OldO» .
Тесты ниже показывают, как это работает:
@Test
public void whenRetryWhenOnError__thenRetryConfirmed() {
TestObserver testObserver = new TestObserver();
Exception noretryException = new Exception("don't retry");
Observable
.error(UNKNOWN__ERROR)
.retryWhen(throwableObservable -> Observable.error(noretryException))
.subscribe(testObserver);
testObserver.assertError(noretryException);
testObserver.assertNotComplete();
testObserver.assertNoValues();
}
@Test
public void whenRetryWhenOnError__thenCompleted() {
TestObserver testObserver = new TestObserver();
AtomicInteger atomicCounter = new AtomicInteger(0);
Observable
.error(() -> {
atomicCounter.incrementAndGet();
return UNKNOWN__ERROR;
})
.retryWhen(throwableObservable -> Observable.empty())
.subscribe(testObserver);
testObserver.assertNoErrors();
testObserver.assertComplete();
testObserver.assertNoValues();
assertTrue("should not retry", atomicCounter.get()==0);
}
@Test
public void whenRetryWhenOnError__thenResubscribed() {
TestObserver testObserver = new TestObserver();
AtomicInteger atomicCounter = new AtomicInteger(0);
Observable
.error(() -> {
atomicCounter.incrementAndGet();
return UNKNOWN__ERROR;
})
.retryWhen(throwableObservable -> Observable.just("anything"))
.subscribe(testObserver);
testObserver.assertNoErrors();
testObserver.assertComplete();
testObserver.assertNoValues();
assertTrue("should retry once", atomicCounter.get()==1);
}
Типичное использование retryWhen - это ограниченные попытки с переменными задержками:
@Test
public void whenRetryWhenForMultipleTimesOnError__thenResumed() {
TestObserver testObserver = new TestObserver();
long before = System.currentTimeMillis();
Observable
.error(UNKNOWN__ERROR)
.retryWhen(throwableObservable -> throwableObservable
.zipWith(Observable.range(1, 3), (throwable, integer) -> integer)
.flatMap(integer -> Observable.timer(integer, TimeUnit.SECONDS)))
.blockingSubscribe(testObserver);
testObserver.assertNoErrors();
testObserver.assertComplete();
testObserver.assertNoValues();
long secondsElapsed = (System.currentTimeMillis() - before)/1000;
assertTrue("6 seconds should elapse",secondsElapsed == 6 );
}
Обратите внимание, как эта логика повторяется три раза и постепенно задерживает каждую попытку.
5. Резюме
В этой статье мы представили несколько способов обработки ошибок и исключений в RxJava.
Есть также несколько специфичных для RxJava исключений, связанных с обработкой ошибок - посмотрите https://github.com/ReactiveX/RxJava/wiki/Error-Handling#rxjava-specific-exceptions-and-what-to-do-about -им[официальная вики]для более подробной информации.
Как всегда, полную реализацию можно найти в over на Github .