W3docs

JavaScript Streams API

Aprenda a Streams API do JavaScript — leia dados progressivamente com ReadableStream, escreva com WritableStream, transforme com TransformStream e encadeie streams para processar grandes volumes de dados com eficiência.

A Streams API permite processar dados em pequenos fragmentos à medida que chegam, em vez de carregar tudo na memória de uma só vez. Isso é essencial para trabalhar com arquivos grandes, respostas lentas de rede e dados em tempo real: você pode começar a processar os primeiros bytes enquanto o restante ainda está em trânsito, e nunca precisa manter toda a carga útil na memória.

A API é construída em torno de três tipos principais. Um ReadableStream é uma fonte da qual você extrai dados. Um WritableStream é um destino para onde você envia dados. Um TransformStream fica no meio, recebendo fragmentos em uma extremidade e emitindo fragmentos modificados na outra. Depois de entender esses três, você pode compô-los em pipelines eficientes.

Lendo um Stream

A maneira mais comum de obter um stream é através da Fetch API. Um objeto Response expõe seu corpo como um ReadableStream por meio de response.body, permitindo consumir o download fragmento a fragmento em vez de aguardar tudo com response.text().

Para leitura manual, chame getReader() para vincular um leitor ao stream e depois itere com reader.read(). Cada chamada resolve para um objeto com done e value:

const response = await fetch('/large-file.txt');
const reader = response.body.getReader();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  // value is a Uint8Array chunk of bytes
  console.log('Received', value.length, 'bytes');
}

Cada value é um Uint8Array — um fragmento de bytes brutos, não uma string (veja Arrays tipados). Quando done é true, o stream terminou e value é undefined. Para converter os bytes em texto, normalmente usa-se um TextDecoder, que consegue unir fragmentos mesmo quando um caractere multibyte está dividido entre duas leituras:

javascript— editable

Esse mesmo loop é a base para construir indicadores de progresso de download: some o tamanho de cada fragmento e compare com o cabeçalho Content-Length.

Iteração Assíncrona

Em ambientes modernos, um ReadableStream é iterável de forma assíncrona, permitindo substituir o loop manual de leitor por for await...of (veja iteradores e geradores assíncronos):

const response = await fetch('/large-file.txt');

for await (const chunk of response.body) {
  // chunk is a Uint8Array
  console.log('Received', chunk.length, 'bytes');
}

Essa abordagem é mais limpa porque o loop cuida de done por você e libera o leitor automaticamente. O problema é o suporte: o Node.js lida bem com isso, mas a iteração assíncrona direta sobre response.body ainda é inconsistente entre navegadores.

Aviso

Como o suporte dos navegadores para iteração assíncrona em streams é inconsistente, o loop com getReader() continua sendo a forma mais portátil. Use for await...of no Node.js ou quando você controla o ambiente de execução; recorra a um leitor no código que precisa funcionar em todos os lugares.

Criando um ReadableStream

Você pode construir sua própria fonte passando um objeto de fonte subjacente ao construtor do ReadableStream. Ele pode definir três métodos opcionais:

  • start(controller) é executado uma vez quando o stream é criado — ideal para configuração ou para enviar dados iniciais.
  • pull(controller) é chamado sempre que o consumidor quer mais dados e a fila interna tem espaço.
  • cancel(reason) é executado se o consumidor parar de ler antes do fim, permitindo fazer limpeza.

Você envia dados com controller.enqueue(chunk) e sinaliza o fim com controller.close():

javascript— editable

Um stream pode carregar qualquer valor JavaScript, não apenas bytes — aqui ele emite números simples. Quando a fonte é lenta ou aberta (um WebSocket, um temporizador, dados de sensor), coloque a lógica em pull() para que os fragmentos sejam produzidos apenas quando o consumidor os solicitar.

TransformStream

Um TransformStream modifica fragmentos à medida que passam por ele. Você fornece uma função transform(chunk, controller) que recebe cada fragmento recebido e chama controller.enqueue() com o resultado transformado:

const upperCaser = new TransformStream({
  transform(chunk, controller) {
    controller.enqueue(chunk.toUpperCase());
  }
});

Um transform stream expõe uma extremidade writable (onde os fragmentos entram) e uma extremidade readable (de onde eles saem), o que é exatamente o que torna o encadeamento possível.

A plataforma fornece vários transforms prontos para uso, de forma que raramente você precisa escrever lógica em nível de bytes manualmente:

  • TextDecoderStream / TextEncoderStream convertem entre fragmentos de bytes e fragmentos de texto.
  • CompressionStream / DecompressionStream aplicam gzip ou deflate em tempo real.

Encadeando Streams

Em vez de conectar leitores e escritores manualmente, você pode conectar streams diretamente. Existem dois métodos:

  • readable.pipeTo(writable) envia cada fragmento de um stream legível para um stream gravável e resolve uma promise ao terminar.
  • readable.pipeThrough(transformStream) passa os dados por um transform e retorna um novo stream legível — perfeito para encadeamento.

Combinando pipeThrough com TextDecoderStream, você obtém fragmentos de texto diretamente de uma resposta de rede, sem precisar gerenciar um decoder manualmente:

const response = await fetch('/large-file.txt');
const textStream = response.body.pipeThrough(new TextDecoderStream());

for await (const textChunk of textStream) {
  console.log(textChunk); // already a string
}

Você pode encadear quantos estágios quiser — por exemplo, response.body.pipeThrough(new DecompressionStream('gzip')).pipeThrough(new TextDecoderStream()) para descomprimir e decodificar em um único pipeline declarativo.

Contrapressão

Uma vantagem fundamental dos streams em relação ao buffer de tudo é a contrapressão (backpressure). Quando o consumidor está lento, o stream automaticamente sinaliza à fonte para pausar a produção e retoma assim que a fila esvazia. Com pipeTo e pipeThrough isso acontece automaticamente — um download rápido não vai ultrapassar uma gravação lenta em disco e estourar a memória.

Informação

A contrapressão é a razão pela qual fazer streaming de um arquivo de vários gigabytes usa apenas uma pequena quantidade limitada de memória. O produtor nunca fica mais do que alguns fragmentos à frente do consumidor, independentemente do tamanho total da carga útil.

Casos de Uso

Os streams se destacam sempre que os dados são grandes, lentos ou contínuos:

  • Renderização progressiva — exiba o início de uma resposta grande enquanto o restante ainda está chegando, em vez de olhar para uma tela em branco.
  • Downloads e uploads com progresso — meça os bytes à medida que fluem para alimentar uma barra de progresso.
  • Processamento de arquivos grandes — processe um arquivo fragmento a fragmento para que a memória permaneça estável mesmo para arquivos maiores que a RAM.
  • Pipelines de compressão — encadeie por CompressionStream ou DecompressionStream para compactar dados em gzip enquanto são transmitidos.

Suporte em Navegadores e Ambientes

ReadableStream, WritableStream e TransformStream têm suporte em todos os navegadores modernos e no Node.js (onde também são expostos via node:stream/web). Os pontos a observar são as adições mais recentes: a iteração assíncrona sobre response.body e o CompressionStream chegaram depois, então verifique o suporte ou forneça um fallback com getReader() quando precisar de ampla cobertura. Os streams estão intimamente relacionados a Blobsblob.stream() retorna um ReadableStream, permitindo integrar objetos similares a arquivos em um pipeline de streaming.

Teste Seus Conhecimentos

Prática
Qual propriedade de uma Response do fetch é um ReadableStream?
Qual propriedade de uma Response do fetch é um ReadableStream?
Prática
Para o que uma chamada reader.read() resolve quando o stream termina?
Para o que uma chamada reader.read() resolve quando o stream termina?
Prática
Qual método passa um stream legível por um transform e retorna um novo stream legível?
Qual método passa um stream legível por um transform e retorna um novo stream legível?
Was this page helpful?