Node.js: Streams no Node.js

Última atualização: 2026-08-26

Charlie recebeu uma tarefa urgente: analisar 2 GB de logs de acesso no servidor. Assim que ele os carregou usando o fs.readFile, a memória esgotou-se imediatamente — causando uma falha por falta de memória (OOM). Seu chefe de equipe disse: “Não importa o tamanho do arquivo, não tente processá-lo todo de uma vez — use o Stream para processá-lo aos poucos!” Charlie mudou para o createReadStream para processar os logs linha por linha, e o uso de memória permaneceu estável em 50 MB. A partir daí, ele passou a dizer a todos que encontrava: “O Stream é o canivete suíço para o processamento de big data no Node.js.”

1. Conceitos básicos de fluxos

Stream é uma interface abstrata do Node.js para o processamento de dados em fluxo contínuo, em que os dados são transmitidos em blocos, como um fluxo de água, em vez de serem carregados na memória de uma só vez.

(1) Quatro tipos de fluxos

Tipo Descrição Cenários típicos Entrada Saída
Legível Fluxo legível, fonte de dados fs.createReadStream, process.stdin Nenhum Sim
Gravável Fluxo gravável, ponto de extremidade de dados fs.createWriteStream, process.stdout Sim Não
Duplex Duplex, leitura/gravação (independente) net.Socket, tls.Socket Sim Sim
Transformar Fluxo de transformação; a saída é uma transformação da entrada zlib.createGzip, crypto.createCipheriv Sim Sim

(2) Dois tipos de fluxo

O fluxo Readable possui dois modos de operação:

Recurso Modo contínuo Modo pausado
Recuperação de dados Envio automático; consumo por meio do evento data É necessário chamar manualmente read() para fazer a recuperação
Método de acionamento Adicionar ouvinte data / Chamar pipe() / Chamar resume() Estado padrão inicial / Chamar pause()
Tratamento da contrapressão pipe() Tratamento automático; a operação manual requer monitoramento drain Ritmo controlado pelo consumidor, contrapressão natural
Casos de uso Fontes de dados contínuas e de alto rendimento Requer controle preciso sobre o tempo de leitura


2. Uma explicação detalhada sobre eventos de stream

Todos os fluxos são baseados em EventEmitter e utilizam um mecanismo orientado a eventos para transmitir dados e estado.

(1) Eventos gerais

Evento Condição de acionamento Fluxo aplicável Parâmetros de retorno de chamada
data Novo bloco recebido Legível chunk
end Dados lidos Legível Nenhum
error Erro Todas as transmissões Error
close Fechar recursos de baixo nível Todas as transmissões Nenhuma
finish end() Após todos os dados terem sido gravados Gravável Nenhum

▶ Exemplo: Monitorando eventos do fluxo “Readable”

JAVASCRIPT
const fs = require('fs');
const rs = fs.createReadStream('./access.log', { highWaterMark: 64 * 1024 });

rs.on('data', (chunk) => {
  console.log(`Received ${chunk.length} Byte`);
});

rs.on('end', () => {
  console.log('Finished reading');
});

rs.on('error', (err) => {
  console.error('Output error:', err.message);
});
▶ Experimente

▶ Exemplo: Monitorando eventos de fluxos graváveis

JAVASCRIPT
const ws = fs.createWriteStream('./output.txt');

ws.on('finish', () => {
  console.log('All data has been written');
});

ws.on('error', (err) => {
  console.error('Write error:', err.message);
});

ws.write('Hello Stream');
ws.end();
▶ Experimente

3. Encadeamento com pipe()

pipe() é o método mais poderoso do Stream; ele conecta automaticamente a saída de um fluxo de leitura à entrada de um fluxo de gravação e lida automaticamente com a contrapressão.

▶ Exemplo:(1) Uso básico do “pipe”

JAVASCRIPT
readable.pipe(writable);
▶ Experimente

pipe() Retorna o fluxo de destino, para que possa ser encadeado:

JAVASCRIPT
readable.pipe(transform1).pipe(transform2).pipe(writable);

▶ Exemplo: Cópia de arquivos

JAVASCRIPT
const fs = require('fs');
fs.createReadStream('./source.txt')
  .pipe(fs.createWriteStream('./dest.txt'));
▶ Experimente

▶ Exemplo: Transmissão de respostas HTTP

JAVASCRIPT
const http = require('http');
const fs = require('fs');

http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  fs.createReadStream('./large.txt').pipe(res);
}).listen(3000);
▶ Experimente

▶ Exemplo:(2) Diagrama de fluxo de dados do tubo de fluxo

100%
flowchart LR
    A[Readable<br/>Data Source] -->|chunk| B[Transform<br/>Data Transformation]
    B -->|chunk| C[Writable<br/>Data Endpoint]
    C -.->|backpressure| A
    B -.->|backpressure| A

    style A fill:#4CAF50,color:#fff
    style B fill:#FF9800,color:#fff
    style C fill:#2196F3,color:#fff


4. Fluxos do sistema de arquivos e operações com arquivos

O módulo fs fornece fluxos de leitura e gravação relacionados ao sistema de arquivos.

(1) createReadStream / createWriteStream

Opção Descrição Padrão
highWaterMark Buffer size (bytes) Readable: 64KB / Writable: 16KB
encoding Codificação null (Buffer)
start Posição do byte inicial 0
end Posição do byte final (inclusive) Infinito
flags Sinais de abertura do arquivo Legível: r / Gravável: w

▶ Exemplo: Como ler um segmento de arquivo dentro de um intervalo especificado

JAVASCRIPT
const fs = require('fs');
const rs = fs.createReadStream('./big.bin', {
  start: 100,
  end: 199,
  highWaterMark: 32
});

rs.on('data', (chunk) => {
  console.log(chunk.length);
});
▶ Experimente

(2) Comparação entre readFile e createReadStream

Comparação readFile createReadStream
Uso de memória Todos os arquivos carregados na memória Utiliza apenas o tamanho do highWaterMark
Atraso na inicialização Aguardar até que todos os arquivos tenham sido lidos antes de chamar a função de retorno Retornar o fluxo imediatamente e processá-lo à medida que for lido
Tamanhos de arquivo adequados Arquivos pequenos (<10 MB) Arquivos grandes ou dados contínuos
Tratamento de erros Recuperação de um erro em uma função de retorno Monitoramento de eventos error
Pode ser pausado/retomado Não é compatível pause() / resume()

▶ Exemplo: Processamento de arquivos grandes bloco por bloco

JAVASCRIPT
const fs = require('fs');
let totalBytes = 0;

const rs = fs.createReadStream('./2gb.log');
rs.on('data', (chunk) => {
  totalBytes += chunk.length;
});
rs.on('end', () => {
  console.log(`Total ${totalBytes} Byte`);
});
▶ Experimente

5. Mecanismo de contrapressão

A contrapressão é fundamental para o controle de fluxo: quando o lado de gravação não consegue acompanhar a taxa de envio do lado de leitura, uma “contrapressão” é aplicada ao lado de gravação para interromper temporariamente o lado de leitura e evitar o acúmulo de dados na memória.

(1) A função pipe() lida automaticamente com a contrapressão

Ao usar o pipe(), a contrapressão é gerenciada automaticamente de forma interna pelo Node.js, sem a necessidade de intervenção manual.

(2) Controle manual da contrapressão

Quando pipe() não for utilizado, é necessário avaliar manualmente o valor de retorno de write():

JAVASCRIPT
const fs = require('fs');
const rs = fs.createReadStream('./source.txt');
const ws = fs.createWriteStream('./dest.txt');

rs.on('data', (chunk) => {
  const canContinue = ws.write(chunk);
  if (!canContinue) {
    rs.pause();
    ws.once('drain', () => {
      rs.resume();
    });
  }
});

rs.on('end', () => {
  ws.end();
});

▶ Exemplo: Observando o efeito da contrapressão

JAVASCRIPT
const rs = fs.createReadStream('./big.log', { highWaterMark: 1024 });
const ws = fs.createWriteStream('./out.log', { highWaterMark: 512 });

let paused = 0;
rs.on('data', (chunk) => {
  const ok = ws.write(chunk);
  if (!ok) {
    paused++;
    rs.pause();
    ws.once('drain', () => rs.resume());
  }
});
rs.on('end', () => {
  console.log(`Backpressure triggered ${paused} times`);
  ws.end();
});
▶ Experimente

6. Transform Stream

Um fluxo Transform é uma subclasse de Duplex; sua saída é o resultado da transformação da entrada, e ele é comumente usado para compressão de dados, criptografia e conversão de formatos.

(1) A diferença entre “Transform” e “Duplex”

Item de comparação Duplex Transformação
Relação entre entrada e saída Independentes; não se influenciam mutuamente A saída é gerada pela transformação da entrada
Métodos obrigatórios _read() + _write() _transform()
Usos típicos Sockets de rede Compressão, criptografia, conversão de dados
Buffer interno Um para leitura, outro para gravação Estado intermediário da transformação

▶ Exemplo: Fluxo de transformação personalizado (conversão de maiúsculas e minúsculas)

JAVASCRIPT
const { Transform } = require('stream');

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

process.stdin.pipe(upper).pipe(process.stdout);
▶ Experimente

▶ Exemplo: arquivos compactados com zlib

JAVASCRIPT
const fs = require('fs');
const zlib = require('zlib');

fs.createReadStream('./access.log')
  .pipe(zlib.createGzip())
  .pipe(fs.createWriteStream('./access.log.gz'));
▶ Experimente

▶ Exemplo: Descompactação de um arquivo zlib

JAVASCRIPT
const fs = require('fs');
const zlib = require('zlib');

fs.createReadStream('./access.log.gz')
  .pipe(zlib.createGunzip())
  .pipe(fs.createWriteStream('./access_restored.log'));
▶ Experimente

7. Como pausar e retomar uma transmissão

O fluxo “Readable” está no modo de pausa por padrão e pode ser ativado ou desativado usando pause() e resume().

▶ Exemplo: Controles de pausa e retomada

JAVASCRIPT
const fs = require('fs');
const rs = fs.createReadStream('./big.log');
let count = 0;

rs.on('data', (chunk) => {
  count++;
  if (count % 10 === 0) {
    rs.pause();
    console.log(`Processed ${count} chunks, pause 1 second`);
    setTimeout(() => rs.resume(), 1000);
  }
});
▶ Experimente

▶ Exemplo: Lendo um arquivo linha por linha (readline)

JAVASCRIPT
const fs = require('fs');
const readline = require('readline');

const rl = readline.createInterface({
  input: fs.createReadStream('./access.log'),
  crlfDelay: Infinity
});

rl.on('line', (line) => {
  if (line.includes('ERROR')) {
    console.log(line);
  }
});

rl.on('close', () => {
  console.log('The file has been read.');
});
▶ Experimente

8. Exemplo abrangente: Pipeline de processamento de logs

Crie um fluxo completo de processamento de dados: Leia arquivos de log grandes → Analise linha por linha → Filtre as linhas com erros → Comprima a saída.

JAVASCRIPT
const fs = require('fs');
const zlib = require('zlib');
const { Transform } = require('stream');

const filterError = new Transform({
  transform(chunk, encoding, callback) {
    const lines = chunk.toString().split('\n');
    const errors = lines.filter(l => l.includes('ERROR')).join('\n');
    callback(null, errors ? errors + '\n' : '');
  }
});

const src = fs.createReadStream('./app.log');
const dest = fs.createWriteStream('./errors.log.gz');

src
  .pipe(filterError)
  .pipe(zlib.createGzip())
  .pipe(dest);

dest.on('finish', () => {
  console.log('The error log has been written in compressed form.');
});

src.on('error', (err) => {
  console.error('Read failed:', err.message);
});

❓ Perguntas Frequentes

P: O que é um stream? R: Um stream é uma interface abstrata para o processamento de dados; os dados podem ser lidos ou gravados em blocos, sem a necessidade de serem carregados na memória de uma só vez.

P: Qual é a diferença entre um fluxo legível (Readable) e um fluxo gravável (Writable)? R: Um fluxo legível (Readable) é uma fonte de dados da qual é possível ler dados; um fluxo gravável (Writable) é um destino de dados no qual é possível gravar dados.

P: Quando se deve usar streams? R: Em situações como o manuseio de arquivos grandes, transmissão de rede e processamento de dados em tempo real — quando o volume de dados é grande ou não pode ser carregado na memória de uma só vez.

P: O que o método pipe faz? R: pipe conecta a saída de um fluxo de leitura à entrada de um fluxo de gravação, gerenciando automaticamente o fluxo de dados e a contrapressão.

P: O que é contrapressão? R: Quando a taxa de saída de dados do fluxo de gravação é menor do que a taxa de entrada de dados do fluxo de leitura, o fluxo de gravação envia um sinal ao fluxo de leitura para interromper a leitura; esse é o mecanismo de contrapressão.


📖 Resumo


📝 Exercícios

  1. Conclua todos os exemplos de código desta lição e certifique-se de que cada um deles seja executado corretamente.
  2. Modifique o exemplo completo e adicione suas próprias extensões
  3. Analise a documentação oficial, identifique 1 ou 2 APIs que não foram abordadas nesta aula e escreva um código de teste para elas.
  4. Reflexão: Como você aplicaria o que aprendeu nesta aula a um projeto do mundo real?
  5. Tente combinar o que você aprendeu nesta aula com o conteúdo das aulas anteriores para criar um pequeno projeto.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%