Fundamentos da Programação Reativa
A programação reativa é um paradigma que trata com fluxos de dados assíncronos e a propagação de mudanças. Em vez de estruturar código imperativo que diz "faça isso, depois aquilo", você declara "quando isso muda, aquilo acontece automaticamente". Esse modelo é especialmente poderoso em aplicações modernas onde eventos, requisições HTTP e interações do usuário ocorrem continuamente e de forma impredizível.
O conceito central é o de Observable — um objeto que representa uma sequência de valores que podem ser emitidos ao longo do tempo. Pense em um Observable como uma TV transmitindo canais: múltiplos espectadores (observadores) recebem os mesmos dados simultaneamente. Essa abordagem simplifica código assincronamente complexo, reduz callbacks aninhados (callback hell) e torna o tratamento de erros mais elegante e centralizado.
RxJS: Implementação Prática em JavaScript
RxJS (Reactive Extensions for JavaScript) é a biblioteca padrão para programação reativa em JavaScript. Ela fornece a implementação de Observables, Observers e Operators — funções que transformam e combinam fluxos de dados. O RxJS integra-se perfeitamente com frameworks como Angular e é amplamente utilizado em aplicações React também.
const { of, interval, from } = require('rxjs');
const { map, filter, take } = require('rxjs/operators');
// Exemplo 1: Observable simples com 'of'
of(1, 2, 3, 4, 5)
.pipe(
filter(x => x % 2 === 0),
map(x => x * 10)
)
.subscribe(
value => console.log('Valor:', value),
error => console.error('Erro:', error),
() => console.log('Completo!')
);
// Output: Valor: 20, Valor: 40, Completo!
// Exemplo 2: Observable contínuo com 'interval'
interval(1000)
.pipe(
take(3)
)
.subscribe(value => console.log('Segundo:', value));
// Output: Segundo: 0, Segundo: 1, Segundo: 2 (a cada segundo)
A sintaxe pipe() é fundamental — permite encadear múltiplos operadores, transformando o fluxo de dados em cada etapa. O subscribe() é onde você define o que fazer com os valores emitidos, erros capturados ou quando o fluxo termina (três callbacks opcionais, nesta ordem).
Operadores Essenciais e Casos de Uso Reais
Operadores Mais Utilizados
RxJS possui mais de 100 operadores. Os mais essenciais para iniciantes são: map() (transforma dados), filter() (seleciona apenas valores que atendem condição), mergeMap() e switchMap() (combinam múltiplos Observables), debounceTime() (aguarda pausa antes de emitir) e catchError() (trata erros).
const { fromEvent } = require('rxjs');
const { debounceTime, map, switchMap } = require('rxjs/operators');
const fetch = require('node-fetch');
// Simulando busca em tempo real (autocomplete)
// Em um navegador real, seria: fromEvent(inputElement, 'input')
const searchInput$ = fromEvent(document.querySelector('#search'), 'input');
searchInput$
.pipe(
debounceTime(300), // Aguarda 300ms de inatividade
map(event => event.target.value),
filter(term => term.length > 2),
switchMap(term =>
fetch(`https://api.example.com/search?q=${term}`)
.then(res => res.json())
),
catchError(error => {
console.error('Erro na busca:', error);
return of([]); // Retorna array vazio em caso de erro
})
)
.subscribe(results => console.log('Resultados:', results));
Este exemplo é típico do mundo real: usuário digita em um campo, você quer fazer requisição HTTP, mas não a cada caractere (custoso). Com debounceTime() e switchMap(), você aguarda o usuário parar de digitar 300ms e depois dispara uma única requisição, cancelando automaticamente qualquer requisição anterior pendente.
Tratamento de Erros e Unsubscribe
Gerenciamento de Memória
Todo Observable que você se inscreve (subscribe) mantém uma conexão na memória. Em aplicações web, especialmente em Single Page Applications (SPAs), é crítico desinscrever quando componentes são destruídos. Caso contrário, você terá vazamento de memória.
const { Subject, Subscription } = require('rxjs');
const { takeUntil } = require('rxjs/operators');
class MinhaClasse {
private destroy$ = new Subject<void>();
private subscriptions: Subscription[] = [];
// Forma 1: Usando takeUntil (recomendado)
subscribe() {
this.minhaFuncao$
.pipe(
takeUntil(this.destroy$)
)
.subscribe(value => console.log(value));
}
// Forma 2: Armazenando subscriptions manualmente
subscribeDirect() {
const sub = this.minhaFuncao$
.subscribe(value => console.log(value));
this.subscriptions.push(sub);
}
ngOnDestroy() {
// Método chamado quando o componente Angular é destruído
this.destroy$.next();
this.destroy$.complete();
this.subscriptions.forEach(sub => sub.unsubscribe());
}
}
O padrão takeUntil() é elegante: você cria um Subject que atua como "gatilho de destruição". Quando seu componente morre, você emite um valor no destroy$, e todos os Observables inscritos com esse operador se desinscreverão automaticamente. Em Angular, o framework moderno oferece o async pipe que gerencia isso para você automaticamente.
// Tratamento de erros com retry
const { retry } = require('rxjs/operators');
minhaRequisicaoAPI$
.pipe(
retry(3), // Tenta novamente até 3 vezes se falhar
catchError(error => {
console.log('Falhou após 3 tentativas:', error);
return of(null);
})
)
.subscribe(data => processarDados(data));
Conclusão
Dominar programação reativa transforma a forma como você escreve código assincronamente. Três aprendizados essenciais: (1) Observables são abstrações poderosas para fluxos de dados que unificam eventos, promises e streams em uma interface consistente; (2) RxJS operators como map, filter e switchMap permitem composição elegante e leitura fluida do código, reduzindo callbacks; (3) Gerenciamento apropriado de subscrições (com takeUntil ou async pipes) é obrigatório para evitar vazamento de memória em aplicações modernas.
A prática deliberada é fundamental — comece com Observables simples (usando of() e interval()), progressive para eventos do DOM, e então combine múltiplos fluxos. A curva de aprendizado é moderada, mas o retorno em clareza e manutenibilidade do código é exponencial.