Diretrizes para o Rx onError

Blog da Tecnologia Netflix em Netflix TechBlog Segue 1 de maio · 4 min ler

De Ed Ballot

“Criar uma boa API é difícil.” – qualquer pessoa que tenha criado uma API usada por outras pessoas

Como acontece com qualquer API, agrupar seu fluxo de dados em um Rx observável requer consideração por um tratamento de erros razoável e um comportamento intuitivo. As diretrizes a seguir destinam-se a ajudar os desenvolvedores a criar uma API consistente e intuitiva.

Como frequentemente criamos Rx Observables em nosso aplicativo para Android, precisávamos de um entendimento comum de quando usar onNext () e quando usar onError () para tornar a API mais consistente para os assinantes. O entendimento divergente é parcialmente porque o nome “onError” é um pouco enganador. O item emitido por onError () não é um erro simples, mas um lance que pode causar danos significativos se não for detectado. Nosso aplicativo tem um manipulador global que impede que ele falhe imediatamente, mas uma exceção não detectada ainda pode deixar partes do aplicativo em um estado imprevisível.

TL; DR – Prefira onNext () e use apenas onError () para casos excepcionais.

Considerações para onNext / onError

A seguir estão pontos a considerar ao determinar se deve usar onNext () versus onError ().

O contrato

Primeiro aqui estão as definições dos dois da página do contrato ReactiveX:

OnNext
transmite um item que é emitido pelo Observável para o observador

OnError
indica que o Observable terminou com uma condição de erro especificada e que não emitirá mais itens

Como apontado na definição acima, uma assinatura é descartada automaticamente após onError (), assim como depois de onComplete (). Por causa disso, onError () deve ser usado apenas para sinalizar um erro fatal e nunca sinalizar um problema intermitente em que se espera que mais dados passem pela assinatura após o erro.

Trate-o como uma exceção

Limite usando onError () para circunstâncias excepcionais, quando você também consideraria lançar um erro ou exceção. O raciocínio é que o parâmetro onError () é um Throwable. Um exemplo de diferenciação: uma consulta de banco de dados que retorna resultados zero normalmente não é uma exceção. O banco de dados que retorna zero resultados porque foi forçosamente fechado (ou colocado em um estado que cancela a consulta em execução) seria uma condição excepcional.

Ser consistente

Não faça seu observável emitir uma mistura de erros determinísticos e não determinísticos. Algo é determinístico se a mesma entrada sempre resultar na mesma saída, como dividir por 0 falhará sempre. Algo não é determinístico se as mesmas entradas puderem resultar em saídas diferentes, como uma solicitação de rede que pode expirar ou retornar resultados antes do tempo limite. O Rx possui métodos de conveniência construídos em torno do tratamento de erros, como retry () (e nosso retryWithBackoff ()). O uso primário de retry () é para reinscrever automaticamente um observável que tenha erros não determinísticos. Quando um observável mistura os dois tipos de erros, torna a repetição menos óbvia, uma vez que repetir as falhas determinísticas não faz sentido – ou é um desperdício, uma vez que a repetição é garantida a falhar. (Duas notas: 1. a repetição também pode ser usada em determinados casos determinísticos como tentativas de login do usuário, em que a falha é causada pela inserção incorreta de credenciais. 2. Para erros mistos, retryWhen () pode ser usado para repetir somente os erros não determinísticos Se você achar que suas necessidades observáveis emitem ambos os tipos de erros, considere se há uma separação apropriada de preocupações. Pode ser que o observável possa ser dividido em vários observáveis, cada um com um objetivo mais direcionado.

Seja consistente com as APIs subjacentes

Ao envolver uma API assíncrona no Rx, considere manter a consistência com o tratamento de erros da API subjacente. Por exemplo, se você estiver envolvendo um sistema de eventos de toque que trata da tela sensível ao toque do dispositivo como uma exceção e encerra a sessão de toque, pode fazer sentido emitir esse erro via onError (). Por outro lado, se tratar da tela sensível ao toque como um evento de dados e permitir que o usuário arraste o dedo de volta para a tela, faz sentido emiti-lo via onNext ().

Evite a lógica de negócios

Relacionado ao ponto anterior. Evite adicionar lógica de negócios que interprete os dados e os converta em erros. O código que o observável está abrindo deve ter a lógica apropriada para realizar essas conversões. Nos casos raros, considere adicionar uma camada de abstração que encapsule essa lógica (para casos normais e de erro) em vez de montá-la no observável.

Detalhes de passagem em onError ()

Se o seu código vai usar onError (), lembre-se que o throwable que ele emite deve incluir dados apropriados para o assinante entender o que deu errado e como lidar com ele.

Por exemplo, nosso manipulador de resposta Falcor usa uma classe FalcorError que inclui o Status do retorno de chamada. Repositórios também poderiam lançar uma extensão desta classe, se detalhes extras precisarem ser incluídos.