Streams Explicados para Desenvolvedores Web
Quando você chama fetch() e aguarda uma resposta, o navegador já está recebendo esses dados em pedaços. A Web Streams API dá ao seu código JavaScript acesso a esses pedaços conforme eles chegam, em vez de esperar que a resposta inteira seja carregada antes de você poder tocá-la.
Essa mudança — de “esperar por tudo” para “processar conforme chega” — é do que se tratam os streams.
Principais Conclusões
- A Web Streams API permite processar dados incrementalmente conforme chegam, em vez de armazenar respostas inteiras na memória.
ReadableStream,WritableStreameTransformStreamsão os três primitivos principais — blocos de construção combináveis para pipelines de dados.response.bodydofetch()é o ponto de entrada mais comum: umReadableStreamque você pode ler pedaço por pedaço.- Use
pipeThrough()epipeTo()para encadear transformações e saídas, com tratamento automático de backpressure integrado.
Por Que Carregar Tudo de Uma Vez É um Problema
A abordagem tradicional para buscar dados se parece com isto:
const response = await fetch('/large-dataset.json')
const data = await response.json()
// Nothing happens until all bytes are downloaded and parsed
Para payloads pequenos, isso funciona bem. Para um arquivo JSON de 50MB ou uma resposta de API de longa duração, você está mantendo a coisa toda na memória antes de processar um único registro. Em dispositivos com recursos limitados ou conexões lentas, isso significa interfaces lentas, alta pressão de memória e usuários frustrados.
Streams permitem que você comece a trabalhar com dados no momento em que o primeiro pedaço chega.
Os Três Primitivos Principais da Web Streams API
A Web Streams API é construída em torno de três classes:
ReadableStream— uma fonte de onde você lê dadosWritableStream— um destino para onde você escreve dadosTransformStream— fica no meio, lendo de um lado e escrevendo dados transformados para o outro
Os dados se movem através desses streams em chunks — pequenos pedaços processados um de cada vez. Um chunk pode ser um Uint8Array de bytes, uma string ou qualquer valor JavaScript, dependendo do stream.
Fetch Streaming: Lendo uma Resposta Incrementalmente
A maioria das respostas fetch() expõe seu corpo como um ReadableStream via response.body. Este é o ponto de entrada mais comum para streams JavaScript para desenvolvedores frontend.
async function processLargeResponse(url) {
const response = await fetch(url)
const reader = response.body.getReader()
const decoder = new TextDecoder()
try {
while (true) {
const { done, value } = await reader.read()
if (done) break
console.log(decoder.decode(value, { stream: true }))
}
} finally {
reader.releaseLock()
}
}
reader.read() retorna uma promise que resolve com { value, done }. Quando done é true, o stream terminou. Esse padrão permite processar uma resposta de vários megabytes pedaço por pedaço, sem armazenar tudo em buffer.
Nota sobre streaming de corpos de requisição: Passar um
ReadableStreamcomo corpo de requisição dofetch()é possível, mas tem suporte irregular nos navegadores. Streaming de respostas é o padrão bem suportado e prático para usar hoje.
Discover how at OpenReplay.com.
Construindo Pipelines de Dados com pipeThrough() e pipeTo()
Onde os streams se tornam genuinamente poderosos é na composição. Você pode encadear um ReadableStream através de uma ou mais instâncias de TransformStream e canalizar o resultado para um WritableStream.
fetch('./data.txt').then((response) =>
response.body
.pipeThrough(new TextDecoderStream())
.pipeThrough(new TransformStream({
transform(chunk, controller) {
controller.enqueue(chunk.toUpperCase())
}
}))
.pipeTo(new WritableStream({
write(chunk) {
document.body.textContent += chunk
}
}))
)
Este pipeline decodifica bytes para texto, transforma cada chunk para maiúsculas, depois escreve no DOM — tudo incrementalmente, sem esperar pela resposta completa.
pipeThrough() conecta um ReadableStream a um TransformStream e retorna um novo ReadableStream. pipeTo() conecta um ReadableStream a um WritableStream e retorna uma promise que resolve quando o stream é concluído.
Backpressure: Como os Streams Evitam Sobrecarga
Quando um consumidor processa dados mais lentamente do que um produtor os gera, os streams aplicam backpressure — um sinal que se propaga de volta através da cadeia de pipes, dizendo à fonte para desacelerar. Isso acontece automaticamente quando você usa pipeTo() e pipeThrough(). É uma das principais razões para preferir piping em vez de ler chunks manualmente em um loop.
Streams Integrados que Vale a Pena Conhecer
O navegador vem com várias utilidades de stream prontas:
TextDecoderStream/TextEncoderStream— convertem entre bytes e stringsCompressionStream/DecompressionStream— comprimem ou descomprimem dados em gzip ou deflate em tempo realBlob.stream()— lê qualquerBlobouFilecomo umReadableStream
O Node.js moderno também suporta a Web Streams API, então os pipelines que você constrói para o navegador transferem facilmente para ambientes server-side.
Conclusão
A Web Streams API oferece aos desenvolvedores frontend uma maneira combinável e eficiente em memória de lidar com dados que chegam ao longo do tempo. ReadableStream e TransformStream são os primitivos que você mais usará — especialmente quando combinados com fetch() para processamento incremental de respostas. Comece com response.body, use pipeThrough() quando precisar transformar dados, e deixe o backpressure cuidar do controle de fluxo para você.
Perguntas Frequentes
Sim. ReadableStream, WritableStream, TransformStream e os métodos de piping são suportados em todos os navegadores modernos, incluindo Chrome, Firefox, Safari e Edge. O streaming de corpos de resposta do fetch via response.body também é amplamente suportado. Corpos de requisição com streaming no fetch têm suporte mais limitado, então verifique as tabelas de compatibilidade antes de confiar nesse recurso.
Se qualquer estágio em uma cadeia de pipes lançar um erro, o erro se propaga através do pipeline. O lado legível fica em estado de erro e o lado gravável é abortado. Você pode lidar com isso passando um objeto de opções com um signal ou capturando a promise retornada de pipeTo. Para loops de leitura manual, envolva suas chamadas de leitura em blocos try-catch.
O Node.js originalmente veio com sua própria API de stream com classes Readable, Writable e Transform. A Web Streams API é um padrão separado projetado para navegadores. Versões modernas do Node.js suportam ambos. A Web Streams API usa um modelo baseado em pull com promises, enquanto os streams clássicos do Node usam um modelo push baseado em eventos. Código escrito contra a Web Streams API é portável entre ambientes de navegador e servidor.
Se a resposta for pequena, digamos menos de algumas centenas de kilobytes, armazenar em buffer com response.json ou response.text é mais simples e perfeitamente eficiente. Streams agregam valor ao lidar com payloads grandes, dados em tempo real, ou situações onde você quer exibir resultados parciais antes da resposta completa chegar. Para chamadas de API diretas retornando JSON compacto, a abordagem tradicional funciona bem.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.