W3docs

Javascript Fetch: progresso de download

Aprenda a rastrear o progresso de download com a Fetch API usando ReadableStream, Content-Length e barras de progresso em JavaScript.

A Fetch API é a forma moderna de fazer requisições de rede em JavaScript, mas await fetch(...) é resolvido assim que os cabeçalhos da resposta chegam — muito antes de o corpo ter terminado de ser baixado. Para exibir uma barra de progresso para um arquivo grande, você precisa ler o corpo de forma incremental e contar os bytes à medida que chegam. Este capítulo explica exatamente como fazer isso com um ReadableStream, por que o Content-Length é importante, como construir uma UI de progresso e o que o fetch não consegue fazer (progresso de upload).

Por que o fetch precisa de um stream para o progresso

Quando você escreve const data = await response.json(), o navegador armazena em buffer o corpo inteiro internamente e só o retorna quando estiver completo — não há como observar os bytes durante a transferência. No entanto, o corpo da Response é exposto como um ReadableStream por meio de response.body. Ao extrair chunks desse stream você mesmo, pode medir quanto chegou e atualizar a UI a cada chunk.

A receita é sempre a mesma:

  1. Obtenha um leitor: const reader = response.body.getReader().
  2. Execute um loop em reader.read(), que é resolvido para { done, value }value é um chunk Uint8Array.
  3. Adicione value.length a um total acumulado e informe o progresso.
  4. Guarde os chunks e remonte-os quando done for true.

Lendo o corpo como um ReadableStream

Para saber a porcentagem, você também precisa do tamanho total. O servidor deve enviar um cabeçalho de resposta Content-Length; leia-o com response.headers.get('Content-Length'). Ele frequentemente está ausente (codificação de transferência em chunks, compressão gzip ou um servidor que simplesmente o omite), portanto o exemplo abaixo usa como fallback um tamanho fornecido pelo chamador.

javascript— editable

A função fetchWithProgress busca dados de uma URL especificada e rastreia o progresso do download. Ela lê o corpo da resposta em chunks usando a interface ReadableStream e chama o callback onProgress com o comprimento recebido e o comprimento total do conteúdo. Os dados baixados são então reconstruídos a partir dos chunks e retornados como uma string decodificada.

Nota

A função tenta ler o cabeçalho Content-Length automaticamente. Se o cabeçalho estiver ausente (comum em transferências em chunks ou com compressão), ela usa o parâmetro fallbackSize como fallback. Em produção, o desenvolvedor ou o servidor deve fornecer esse tamanho de fallback. Para arquivos grandes, evite armazenar todos os chunks em memória; processe-os de forma incremental ou use response.blob() para respostas que não sejam texto.

Quando o loop terminar, você terá um array de chunks Uint8Array. Para transformá-los em dados utilizáveis, copie-os em um único buffer e decodifique (para texto) ou envolva-os em um Blob (para arquivos binários, imagens, downloads). Consulte JavaScript Blob para trabalhar com dados binários e disparar downloads de arquivos.

Exibindo o progresso para os usuários

Uma barra de progresso é apenas um elemento cuja largura acompanha received / total. Passe um callback updateProgressBar no lugar do de logging:

<body>
  <div id="progress-bar" style="width: 100%; background-color: #e0e0e0;">
    <div id="progress" style="width: 0; height: 20px; background-color: #76c7c0;"></div>
  </div>
  <div id="output"></div>
  <script>
    function updateProgressBar(received, total) {
      const progressElement = document.getElementById('progress');
      const percentage = Math.min(100, (received / total) * 100);
      progressElement.style.width = percentage + '%';
    }
    document.addEventListener('DOMContentLoaded', () => {
      const url = 'https://api.w3docs.com/uploads/media/default/0001/05/dd10c28a7052fb6d2ff13bc403842b797a73ff3b.txt';
      const size = 3_900_000; // fallback size
      // fetchWithProgress is defined in the previous code block
      fetchWithProgress(url, updateProgressBar, size)
      .then(data => {
        document.getElementById('output').textContent = 'File content: ' + data.slice(0, 1000) + '...';
      })
      .catch(err => console.error("Download failed:", err));
    });
  </script>
</body>
Aviso

Se você estiver baixando um arquivo muito grande, evite atualizar a UI com frequência excessiva. Por exemplo, em vez de atualizar a barra de progresso a cada chunk individual, você pode atualizá-la com menos frequência (por exemplo, a cada alguns chunks ou com base em um intervalo de tempo). Isso ajuda a manter sua UI leve.

Os elementos HTML no exemplo acima configuram uma barra de progresso para visualizar o progresso do download e uma área de texto pré-formatada para exibir o conteúdo baixado. O div progress-bar serve como contêiner, e o div progress representa o progresso real do download.

O código JavaScript atualiza a barra de progresso com base no comprimento dos dados recebidos e no comprimento total do conteúdo. A função updateProgressBar calcula a porcentagem dos dados baixados e ajusta a largura da barra de progresso de acordo. O listener de evento dispara a função fetchWithProgress no carregamento da página, atualizando a barra de progresso e exibindo o conteúdo baixado no elemento output.

Observação: fetchWithProgress é definida no trecho anterior. Em um projeto real, certifique-se de que ela esteja no escopo (por exemplo, via importações de módulos, bundling ou uma tag de script global).

O que o fetch não consegue fazer: progresso de upload

A técnica de stream rastreia apenas o progresso de download (resposta). Até o momento, não há forma padrão de observar o progresso de upload (corpo da requisição) com fetch — o corpo da requisição não é exposto como um stream observável nos navegadores. Se você precisar de uma barra de progresso ao enviar um arquivo, recorra ao XMLHttpRequest, cujo objeto upload dispara eventos progress:

function uploadWithProgress(url, file, onProgress) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('POST', url);

    // upload.onprogress fires repeatedly while the body is sent
    xhr.upload.onprogress = (event) => {
      if (event.lengthComputable) {
        const percent = Math.round((event.loaded / event.total) * 100);
        onProgress(event.loaded, event.total, percent);
      }
    };

    xhr.onload = () => resolve(xhr.responseText);
    xhr.onerror = () => reject(new Error('Upload failed'));
    xhr.send(file);
  });
}

event.lengthComputable informa se event.total é conhecido; sempre verifique esse valor antes de calcular uma porcentagem.

Cancelando um download em andamento

Um download longo deve ser cancelável. Passe um AbortSignal para o fetch e chame controller.abort() para interromper tanto a requisição quanto o loop do stream — consulte Fetch: Abort para o padrão completo.

Dicas profissionais

  • Trate dados binários: TextDecoder funciona apenas para texto. Use response.blob() ou response.arrayBuffer() para arquivos binários.
  • Limite as atualizações de progresso: Leituras rápidas de chunks podem bloquear a thread da UI. Faça throttle ou debounce no callback de progresso.
  • Gerenciamento de memória: Evite armazenar todos os chunks em um array para arquivos grandes. Processe-os de forma incremental.
  • Fallback do Content-Length: O cabeçalho frequentemente está ausente. Sempre forneça um fallbackSize confiável.
  • Otimize o feedback ao usuário: Fornecer feedback em tempo real sobre o progresso do download melhora a satisfação do usuário e pode tornar sua aplicação mais responsiva.
  • Aproveite as capacidades do navegador: Navegadores diferentes podem ter suporte variado a recursos avançados. Teste sua implementação em múltiplos navegadores para garantir compatibilidade.
  • Simplifique quando possível: Para downloads em que o progresso via streaming não é estritamente necessário, response.arrayBuffer() oferece uma forma mais simples de reconstruir dados sem concatenar chunks manualmente.

Com esses insights e exemplos, você está agora preparado para implementar a Fetch API com rastreamento de progresso de download em seus projetos, oferecendo aos usuários uma experiência fluida e informativa.

Conclusão

Dominar a Fetch API envolve entender não apenas como fazer requisições básicas, mas também como lidar com cenários mais avançados, como rastrear o progresso de download. Ao usar ReadableStreams, podemos monitorar e fornecer feedback sobre downloads, melhorando significativamente a experiência do usuário. Implementar essas técnicas garantirá que suas aplicações sejam robustas, amigáveis ao usuário e capazes de lidar com grandes transferências de dados com eficiência.

Prática

Prática
Quais são as etapas principais para rastrear o progresso de download usando a Fetch API?
Quais são as etapas principais para rastrear o progresso de download usando a Fetch API?
Was this page helpful?