RxJava e tratamento de erros

RxJava e tratamento de erros

1. Introdução

Neste artigo, veremos como lidar com exceções e erros usando o RxJava.

Primeiro, lembre-se de que o Observable normalmente não gera exceções. Em vez disso, por padrão, Observable chama o método onError () _ do seu _Observer, notificando o observador de que um erro irrecuperável acabou de ocorrer e, em seguida, é encerrado sem chamar mais nenhum dos métodos do seu _Observer’s.

*Os operadores de manipulação de erros que estamos prestes a introduzir alteram o comportamento padrão ao retomar ou tentar novamente a sequência _Observable_.

===* 2. Dependências do Maven *

Primeiro, vamos adicionar o RxJava no pom.xml:

<dependency>
    <groupId>io.reactivex.rxjava2</groupId>
    <artifactId>rxjava</artifactId>
    <version>2.1.3</version>
</dependency>

===* 3. Manipulação de erros *

Quando ocorre um erro, geralmente precisamos lidar com isso de alguma maneira. Por exemplo, altere estados externos relacionados, continuando a sequência com resultados padrão ou simplesmente deixe-o para que o erro possa se propagar.

====* 3.1 Ação em caso de erro *

Com doOnError, podemos invocar qualquer ação necessária quando houver um erro:

@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());
}

No caso de uma exceção ser lançada durante a execução da ação, o RxJava quebra a exceção em 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 Continuar com itens padrão *

Embora possamos invocar ações com doOnError, o erro ainda interrompe o fluxo de sequência padrão. Às vezes, queremos retomar a sequência com uma opção padrão, é o que 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");
}

Se um fornecedor de item padrão dinâmico for preferido, podemos usar o 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 Continuar com outra sequência *

Em vez de retornar a um único item, podemos fornecer a sequência de dados de fallback usando onErrorResumeNext ao encontrar erros. Isso ajudaria a impedir a propagação de erros:

@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");
}

Se a sequência de fallback diferir de acordo com os tipos de exceção específicos, ou a sequência precisar ser gerada por uma função, podemos passar a função para o _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 Manipular Exceção * Apenas

O RxJava também fornece um método de fallback que permite continuar a sequência com um Observable fornecido quando uma exceção (mas nenhum erro) é gerada:

@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();
}

Como mostra o código acima, quando ocorre um erro, o onExceptionResumeNext não entra em ação para retomar a sequência.

*4. Repetir com erro *

A sequência normal pode ser interrompida por uma falha temporária do sistema ou erro de back-end. Nessas situações, queremos tentar novamente e esperar até que a sequência seja corrigida.

Felizmente, o RxJava nos dá opções para fazer exatamente isso.

====* 4.1 Repetir *

Ao usar http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Observable.html#retry--[retry_ξ,, o _Observable será reinscrito várias vezes até que não haja erro. Mas, na maioria das vezes, preferimos uma quantidade fixa de novas tentativas:

@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 Repetir sob condição *

A tentativa condicional também é viável no RxJava, usando retry com predicados ou usando _http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Observable.html#retryUntil-io.reactivex.functions.BooleanSupplier- [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 *

Além dessas opções básicas, há também um método interessante de nova tentativa: retryWhen.

Isso retorna um Observable, _ diga _ “NewO”, _ que emite os mesmos valores da origem _ObservableSource