[Nodejs] Ahh essas Streams

Sinceramente eu não sei porquê a galera tem tanto medo de Streams e porquê sempre entra nos tópicos de "Coisa super mega avançada com Node", tem milhares de artigos, tutoriais, vídeos e afim sobre streams (ainda mais em Node) então serei simples e direto com uns exemplos meio diferentaços.

O que são, como vivem e o que comem? Stream é nada mais que um "pedaço" de um binário (escrevi binário porque pode ser qualquer coisa). Quais ganhos temos com o uso de streams então? Simples, se eu tenho um arquivo ultra pesado e preciso, por exemplo, manipular os dados de alguma forma. Ao invés de ler o arquivo inteiro e fazer todas as operações em memória e depois salvar tudo novamente, com o uso de stream, no momento que você começa a leitura do arquivo você já tem acesso àquele pedaço (chunk) de informação, permitindo que você já faça o tratamento que deseja e já escreva em um novo arquivo com as alterações, tudo ao mesmo tempo enquanto está lendo o arquivo inicial.

Em Node temos quatro tipo de streams:

- Writable: Para escrever

- Readable: Para ler

- Duplex: Ler e escrever

- Transform: É uma Duplex que te permite modificar dados (o exemplo lá de cima)

Então vamos ao código:

const { Readable } = require('stream');

function* gen() {
    yield 'Toda vez';
    yield new Promise(resolve => setTimeout(() => resolve('que eu chego em casa'), 1000));
    yield 'A barata da vizinha';
    yield new Promise(resolve => setTimeout(() => resolve('está na minha cama'), 2000));
}

Readable.from(gen()).on('data', chunk => console.log(chunk));

Lembra que eu falei que dá para streamar qualquer coisa? Bem, aqui a gente tem um generator (gen) que está simulando algum processamento hard ou latência de outro canto que demora para obter resultados, usando o módulo Readable eu consigo transformar esse generator em uma stream e enquanto ele tiver dados para transmitir eu vou processando um em um sem bloquear e sem botar N coisas em memória.

Voltando ao exemplo de cima, vamos pensar que aquele processamento é converter todo um arquivo para letras maiúsculas, temos:

const { createReadStream, createWriteStream } = require('fs');
const { pipeline, Transform } = require('stream');

const uppercase = new Transform({
    transform(chunk, encoding, callback) {
        callback(null, chunk.toString().toUpperCase());
    },
});

pipeline(
    createReadStream('./tudoMinusculo.txt'),
    uppercase,
    createWriteStream('./tudoMaiusculo.txt'),
    err => err ? console.log('Deu ruim') : console.log('Deu Bom')
);

Temos a função pipeline que funciona como uma pipeline (ahh vá), vocês também poderiam utilizar o ".pipe" individualmente na sua stream para cada passo (mas eu acho essa sintaxe mais bonita). Nele vamos ter cada "step" que nosso chunk vai passar e no final de tudo, saber se rolou ou não (é possível transformar em promise fácil fácil com promisify).

Então, nossa pipeline começa gerando uma stream Readable (createReadStream do módulo fs) do nosso arquivo "tudoMinusculo.txt", a pipeline pega esse chunk e passa para outra stream dessa vez uma Transform (você pode escrever usando uma classe para ter mais flexibilidade e estender mais fácil) que realiza o upperCase, então passa para outra stream, nesse caso uma Writable (createWriteStream do módulo fs) que vai salvando (escrevendo) em um novo arquivo, caso não dê nenhum erro, temos o OK.

Bem, acho isso é um ótimo começo para ir se aprofundando e ver o quanto de coisas que dá para fazer. Streams em Nodejs é algo muuuuuito poderoso (além do mais Node foi criado justamente para isso) e você consegue fazer de várias formas. Entre de cara nos diversos conteúdos que existem e veja o melhor para o seu caso, insira mais streams na sua vida se você trabalha com Node pois isso é quase o coração da coisa... Abraços!