Redux Observable — Programação Reativa com Épicos

Gerenciar o estado no mundo do JavaScript e React é difícil, mas usando o Redux para gerenciar seu estado, as coisas ficam muito mais fáceis. Lidar com código assíncrono, no entanto, o torna muito mais complexo. Mas vamos tentar facilitar sua vida usando o poder da programação reativa para gerenciar suas ações assíncronas.

Redux — Actions e Reducers

O Redux te ajuda a gerenciar dados no estado do seu aplicativo de maneira controlada.
Vamos começar com uma introdução rápida, caso você não esteja familiarizado com isso.

O Redux fornece gerenciamento de estado previsível usando actions e reducers.
O estado pode ser alterado apenas enviando actions para o reducer.
Uma action é um objeto que descreve o que deve ou deve acontecer, mas não especifica como deve ser feito.

Ele tem um tipo e pode ter uma carga útil com dados adicionais.
O exemplo a seguir mostra um criador de uma action, que é uma função que cria e retorna uma action:

Um reducer é uma função que decide como a action deve alterar o estado. Ele pega o estado atual e a action como entradas e retorna um novo estado.

No exemplo a seguir, isFetching será alterado para true quando a ação com o tipo FETCH_DATA for enviada ao redutor e isso poderá resultar na exibição de um carregador. A action com o tipo FETCH_DATA_SUCCESS mudará isFetching de volta para false e também incluirá os dados da carga útil da action no estado para que possam ser mostrados ao usuário.

As transições de estado são tratadas de forma síncrona pelos reducers, o que significa que você precisa cuidar dos dados naquele momento e no momento em que a action é despachada.

E se você quiser atrasar as coisas ou alterar alguma coisa quando receber uma resposta de um servidor? Como você pode gerenciar actions assíncronas e efeitos colaterais de uma maneira boa em um aplicativo Redux?

Tratando actions assíncronas

O redux por si só não pode cuidar da sua lógica assíncrona, mas você pode colocar essa lógica em um middleware, que são funções que têm acesso às actions despachadas e potencialmente podem despachar novas actions.

No exemplo abaixo, usaremos o Redux Thunk, um middleware para Redux que permite escrever criadores de actions que retornam uma função em vez de uma action.
Isso pode ser usado para verificar uma condição antes de despachar uma action ou para atrasar o despacho por um determinado período de tempo.

O Thunk permite controlar quando e se uma action deve ser despachada e funciona bem em muitos casos.
Porém, para cenários mais complexos em que precisamos ter mais controle, alguns problemas persistem.

Você pode, por exemplo, não mudar de idéia e cancelar coisas que já foram acionadas porque as promises não podem ser canceladas.

Mas porque você gostaria de cancelar uma promise?

Vamos analisar novamente o exemplo acima: onde fazemos uma visita a uma lanchonete. Você está entrando e pedindo um sanduíche, mas depois de fazer o pedido, lamentou sua decisão e gostaria de cancelar o pedido.
Mas o problema é que o chef não pode parar de preparar seu pedido. Quando ele recebe um pedido, ele precisa concluí-lo e fazer o sanduíche inteiro.
Da mesma forma, você gostaria de cancelar uma promise desencadeada por uma action que o usuário lamenta.

Digamos, por exemplo, que o usuário navegue para uma página, que solicite alguns dados, mas depois mude de idéia e volte.
Você ainda precisará lidar com a resposta quando ela chegar, mesmo que esse componente, por exemplo, possa ter sido desmontado.

Portanto, a questão permanece:
como podemos resolver um problema como esse?

A solução para isso pode ser encontrada com a programação reativa. Se você já ouviu falar de programação reativa antes, mas nunca entendeu o conceito, esse artigo é pra você.
Começaremos abaixo uma breve introdução à programação reativa.

Programação Reativa

Dois termos importantes quando se trata de programação reativa são observables e streams. Um observable modela um stream ( fluxo) de eventos. Isso pode ser descrito com mais facilidade pela ilustração abaixo. É uma combinação de linhas e círculos.
As linhas representam a linha do tempo de um stream de eventos. Esses eventos são representados pelos círculos .

Digamos que estamos solicitando alguns dados, com cada solicitação representada pelos círculos na linha do tempo.
Queremos continuar solicitando os dados até recebermos todos os blocos de dados (a-g).
Pense no problema descrito anteriormente em que um usuário navega para uma página, mas o usuário muda de idéia e navega de volta.
Nesse caso, podemos cancelar imediatamente quando o evento z ocorrer, mesmo que todos os dados não tenham sido recebidos.

código Assíncrono e efeitos colaterais com Redux-Observable

O Redux-Observable é um middleware para Redux que lida com cancelamentos e muitos outros efeitos colaterais assíncronos usando programação reativa. Cenários complexos, como outras ações que afetam a forma como uma solicitação de rede pendente deve ser tratada, podem ser resolvidos facilmente com o Redux-Observable.

RxJS e Most.js são duas bibliotecas de programação reativa com as quais você pode manipular fluxos de ações de maneiras diferentes.
O RxJS é o mais popular dos dois, mas o Redux-Observable também pode ser usado com o Most.js, que é menor e mais rápido.
Nos exemplos a seguir, Most.js será usado.

Então, como o Redux-Observable combina programação reativa com o Redux?

Épico — Actions entram, actions saem

O conceito de épico é a parte principal do Redux-Observable.

Um épico é uma função que executa um fluxo de actions e retorna um fluxo de actions. Todas as action despachadas entram no fluxo de actions.

Você pode olhar para o tipo de ação e decidir se deseja fazer alguma coisa. Uma ação entra e novas ações podem ser despachadas. Actions entram— actions saem.

Vamos dar uma olhada em um épico simples da documentação do Redux-Observable:

Temos o stream de actions como entrada.
Se recebermos uma ação do tipo PING, queremos gerar uma ação do tipo PONG.
Para mostrar um exemplo fácil do que você pode fazer com a programação reativa, também adicionamos um atraso de 1000 ms antes de despacharmos a action PONG.

O flow do Redux e Redux-Observable

Uma ação com o tipo PING está sendo despachada.
Atinge o store Redux, onde temos a função reducer, que toma o estado atual e a action como entrada e gera um novo estado.
Nesse caso, quando recebemos a action PING, o reducer mudará o estado, então isPingingserá definido como true. Então a action chegará ao nosso épico, que após um segundo produzirá a action PONG. Essa action é enviada ao reducer, que isPinging de volta para false.

Redux-Observable é ótimo para cenários complexos

O exemplo anterior foi muito simples.
Para realmente ver os benefícios do Redux-Observable, precisamos torná-lo um pouco mais complexo.
Digamos que você queira pesquisar periodicamente um serviço para verificar o status atual, que pode ser IN_PROGRESS, COMPLETE,ou ERROR.
O statusCheckEpic usa o fluxo de actions como entrada e filtra o tipo de action POLL_SERVICE_STATUS.
Se essa action for despachada, queremos fazer uma solicitação para o endpoint de status do serviço.
A função checkServiceStatus () fará a solicitação e retorna uma promise, e um fluxo Observable será criado no épico dessa promise.

Se a promise for cumprida, o fluxo conterá a resposta. Se o status recebido for IN_PROGRESS, não queremos fazer nada além de continuar aguardando e solicitar periodicamente o status. Portanto, podemos filtrar essas respostas no épico. Se a resposta tiver o status COMPLETE, usaremos o criador da ação serviceComplete(), que criará uma action com o tipo SERVICE_COMPLETEque será despachada e poderá atualizar o estado para mostrar o status ao usuário no aplicativo.

Se o status for outro, uma ação com o tipo SERVICE_ERROR será despachada.
A mesma action será despachada se ocorrer um erro, mas para isso, precisamos usar recoverWithpara recuperar a falha do fluxo e criar um novo stream de Observables.

Repetindo a request periodicamente

Se você testar o código acima e recuperar o status IN_PROGRESS, nada mais vai acontecer..
Não é isso o que queremos! Queremos continuar solicitando o status com um certo intervalo. É fácil de corrigir no Redux-Observable.

Ao agrupar todo o nosso código com most.periodic(1000), repetiremos tudo a cada 1000 ms e, portanto, obteremos pollings periódicos. Também adicionaremos take(1) no final, o que significa que, assim que obtivermos uma ação como saída do flatMap interno (SERVICE_COMPLETE ou SERVICE_ERROR), interromperemos o polling periódica.

Cancelando o polling

E se o usuário voltar à página anterior?
Como podemos garantir que as solicitações de serviço sejam interrompidas?
Seria muito difícil usar outras alternativas, mas com Redux-Observable e Most.js é tão simples quanto adicionar uma linha de código extra:
.takeUntil(action$.filter(action=>action.type===NAVIGATE_BACK))

Isso significa que nosso stream de actions vai continuarNAVIGATE_BACK (ou acontecer um responseSERVICE_COMPLETE ou SERVICE_ERROR como antes).

O épico completo tanto com o polling periódico quando como o cancelamento:

Redux-Observable faz épicos assíncronos

Como esses exemplos demonstraram, você pode adicionar lógica assíncrona complexa com bastante facilidade com o Redux-Observable e ainda manter uma estrutura de código legível.

Você pode combinar diferentes fluxos de actions, transformá-los, alterar o tempo adicionando delays, lidar com cancelamento, etc.

Embora exista um pouco de curva de aprendizado, você está agora um passo mais perto e, quando o obtém, basicamente apenas é necessário adicionar algumas linhas de código a mais para obter muitas funcionalidades extras.

Créditos

Epic Reactive Programming with Redux-Observable escrito por Tomas Nilsson

Software Engineer | Front-end Specialist @ Grover (https://rubenmarcus.dev)

Software Engineer | Front-end Specialist @ Grover (https://rubenmarcus.dev)