Skip to content

reduce - Emite Apenas o Resultado Acumulado Final

O operador reduce aplica uma função cumulativa a cada valor no stream e emite apenas o resultado cumulativo final na conclusão do stream. Funciona da mesma forma que Array.prototype.reduce para arrays, sem emitir resultados intermediários.

🔰 Sintaxe Básica e Uso

ts
import { of } from 'rxjs';
import { reduce } from 'rxjs';

of(1, 2, 3, 4, 5)
  .pipe(reduce((acc, curr) => acc + curr, 0))
  .subscribe(console.log);

// Saída: 15 (apenas resultado final)
  • acc é o valor cumulativo, curr é o valor atual.
  • Os valores são acumulados sequencialmente, começando pelo valor inicial (0 neste caso).
  • Nenhum valor é emitido até que o stream seja concluído, e apenas o resultado final é emitido na conclusão.

🌐 Documentação Oficial do RxJS - reduce

💡 Padrões de Uso Típicos

  • Calcular somas, médias, máximos e mínimos de números
  • Agregar e transformar objetos
  • Construir ou combinar arrays
  • Quando apenas o resultado de agregação final é necessário

🔍 Diferença em relação a scan

OperadorMomento da EmissãoConteúdo da EmissãoUso
reduceApenas uma vez na conclusãoResultado cumulativo finalAgregação onde apenas o resultado final é necessário
scanToda vez para cada valorTodos incluindo resultados intermediáriosAgregação em tempo real/gerenciamento de estado
ts
import { of } from 'rxjs';
import { reduce, scan } from 'rxjs';

const source$ = of(1, 2, 3, 4, 5);

console.log('=== reduce ===');
source$.pipe(
  reduce((acc, curr) => acc + curr, 0)
).subscribe(console.log);
// Saída: 15

console.log('=== scan ===');
source$.pipe(
  scan((acc, curr) => acc + curr, 0)
).subscribe(console.log);
// Saída: 1, 3, 6, 10, 15

🧠 Exemplo de Código Prático (com UI)

Este exemplo soma os valores de vários campos de entrada e exibe o resultado final ao clicar em um botão.

ts
import { fromEvent, from } from 'rxjs';
import { map, reduce, switchMap } from 'rxjs';

// Criar campos de entrada
const inputs: HTMLInputElement[] = [];
for (let i = 1; i <= 3; i++) {
  const label = document.createElement('label');
  label.textContent = `Valor ${i}: `;
  const input = document.createElement('input');
  input.type = 'number';
  input.value = '0';
  label.appendChild(input);
  document.body.appendChild(label);
  document.body.appendChild(document.createElement('br'));
  inputs.push(input);
}

// Botão de calcular
const button = document.createElement('button');
button.textContent = 'Calcular Soma';
document.body.appendChild(button);

// Área de exibição do resultado
const output = document.createElement('div');
output.style.marginTop = '10px';
document.body.appendChild(output);

// Calcular soma ao clicar no botão
fromEvent(button, 'click').pipe(
  switchMap(() => {
    // Obter todos os valores de entrada
    const values = inputs.map(input => Number(input.value) || 0);
    return from(values).pipe(
      reduce((acc, curr) => acc + curr, 0)
    );
  })
).subscribe(total => {
  output.textContent = `Total: ${total}`;
  console.log('Total:', total);
});
  • Ao clicar no botão, todos os valores de entrada são somados e apenas o total final é exibido.
  • Resultados intermediários não são emitidos.

🎯 Exemplo de Agregação de Objetos

Este é um exemplo prático de agregar múltiplos valores em um objeto.

ts
import { from } from 'rxjs';
import { reduce } from 'rxjs';

interface Product {
  category: string;
  price: number;
}

const products: Product[] = [
  { category: 'Comida', price: 500 },
  { category: 'Bebida', price: 200 },
  { category: 'Comida', price: 800 },
  { category: 'Bebida', price: 150 },
  { category: 'Comida', price: 300 },
];

// Agregar preço total por categoria
from(products).pipe(
  reduce((acc, product) => {
    acc[product.category] = (acc[product.category] || 0) + product.price;
    return acc;
  }, {} as Record<string, number>)
).subscribe(result => {
  console.log('Total por categoria:', result);
});

// Saída:
// Total por categoria: { Comida: 1600, Bebida: 350 }

🎯 Exemplo de Construção de Array

Aqui está um exemplo de combinar valores de stream em um array.

ts
import { interval } from 'rxjs';
import { take, reduce } from 'rxjs';

interval(100).pipe(
  take(5),
  reduce((acc, value) => {
    acc.push(value);
    return acc;
  }, [] as number[])
).subscribe(array => {
  console.log('Array coletado:', array);
});

// Saída:
// Array coletado: [0, 1, 2, 3, 4]

TIP

Ao construir um array, considere usar o operador toArray mais conciso.

ts
interval(100).pipe(
  take(5),
  toArray()
).subscribe(console.log);
// Saída: [0, 1, 2, 3, 4]

💡 Utilizando reduce Type-Safe

Aqui está um exemplo de utilizar a inferência de tipos do TypeScript.

ts
import { from } from 'rxjs';
import { reduce } from 'rxjs';

interface UserAction {
  type: 'click' | 'scroll' | 'input';
  timestamp: number;
}

const actions: UserAction[] = [
  { type: 'click', timestamp: 100 },
  { type: 'scroll', timestamp: 200 },
  { type: 'click', timestamp: 300 },
  { type: 'input', timestamp: 400 },
];

const actions$ = from(actions);

// Agregar contagem por tipo de ação
actions$.pipe(
  reduce((acc, action) => {
    acc[action.type] = (acc[action.type] || 0) + 1;
    return acc;
  }, {} as Record<UserAction['type'], number>)
).subscribe(result => {
  console.log('Agregação de ações:', result);
});

// Saída:
// Agregação de ações: { click: 2, scroll: 1, input: 1 }

⚠️ Notas

❌ Streams Infinitos Não Completam (Importante)

WARNING

reduce não emitirá um único valor até que complete() seja chamado. Streams infinitos (interval, fromEvent, etc.) causam acidentes na prática, já que nenhum valor fica permanentemente disponível.

ts
import { interval } from 'rxjs';
import { reduce } from 'rxjs';

// ❌ Exemplo ruim: Stream infinito, então nenhum valor é emitido
interval(1000).pipe(
  reduce((acc, curr) => acc + curr, 0)
).subscribe(console.log);
// Sem saída (stream não completa)

Contramedida 1: Use scan quando agregação contínua é necessária

ts
import { interval, scan, take } from 'rxjs';

// ✅ Bom exemplo: Obter resultados intermediários em tempo real
interval(1000).pipe(
  take(5),
  scan((acc, curr) => acc + curr, 0)
).subscribe(console.log);
// Saída: 0, 1, 3, 6, 10 (emite valor cumulativo toda vez)

Contramedida 2: Se apenas o valor final é necessário, use scan + takeLast(1)

ts
import { interval, scan, take, takeLast } from 'rxjs';

// ✅ Bom exemplo: Acumular com scan, obter apenas valor final
interval(1000).pipe(
  take(5),
  scan((acc, curr) => acc + curr, 0),
  takeLast(1)
).subscribe(console.log);
// Saída: 10 (apenas resultado final)

Contramedida 3: Use take para especificar a condição de término

ts
import { interval, take, reduce } from 'rxjs';

// ✅ Bom exemplo: Definir condição de término com take
interval(1000).pipe(
  take(5),
  reduce((acc, curr) => acc + curr, 0)
).subscribe(console.log);
// Saída: 10

TIP

Critérios de Seleção:

  • Resultados intermediários são necessários → scan
  • Apenas o resultado final é necessário & conclusão do stream é garantida → reduce
  • Apenas resultado final necessário & stream infinito → scan + takeLast(1) ou take + reduce

Uso de Memória

Quando o valor cumulativo é um objeto grande ou array, o uso de memória deve ser levado em conta.

ts
// Exemplo requerendo atenção à memória
from(largeDataArray).pipe(
  reduce((acc, item) => {
    acc.push(item); // Acumular grandes quantidades de dados
    return acc;
  }, [])
).subscribe();

📚 Operadores Relacionados

  • scan - Emite um resultado intermediário para cada valor
  • toArray - Combinar todos os valores em um array
  • count - Conta o número de valores
  • min / max - Obter valores mínimo e máximo

Resumo

O operador reduce acumula todos os valores em um stream e emite apenas o resultado final na conclusão. Isso é adequado quando resultados intermediários não são necessários e apenas o resultado de agregação final é necessário. No entanto, como nenhum resultado é obtido se o stream não for concluído, você deve usar scan para streams infinitos ou definir uma condição de saída com take ou similar.

Lançado sob a licença CC-BY-4.0.