Skip to content

Javascript Fetch: download progress

In modern web development, efficiently handling HTTP requests is paramount. The Fetch API has become the go-to solution for making network requests in JavaScript. This article delves into mastering the Fetch API, focusing on how to track download progress. We will explore various methods, provide real-world code examples, and share professional tips to enhance your understanding and implementation of the Fetch API.

Tracking Download Progress

One of the advanced features of the Fetch API is the ability to track the progress of a download. This is particularly useful for large files or when providing user feedback during lengthy operations.

Using ReadableStreams

To track download progress, we can utilize the ReadableStream interface. This allows us to read the response body in chunks and monitor the progress.

Note: ReadableStream is widely supported in modern browsers. For legacy browser support, consider using the XMLHttpRequest API with its onprogress event.


Output appears here after Run.

The function fetchWithProgress fetches data from a specified URL and tracks the download progress. It reads the response body in chunks using the ReadableStream interface and calls the onProgress callback with the received length and total content length. The downloaded data is then reconstructed from the chunks and returned as a decoded string.

note

The function attempts to read the Content-Length header automatically. If the header is missing (common with chunked transfers or compression), it falls back to the fallbackSize parameter. In production, the developer or server must provide this fallback size. For large files, avoid buffering all chunks in memory; process them incrementally or use response.blob() for non-text responses.

Displaying Progress to Users

To provide feedback to users, we can integrate a progress bar that updates as the download progresses.


html
<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://www.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>

WARNING

If you are downloading a very large file, avoid updating the UI too frequently. For example, instead of updating the progress bar for every single chunk, you can update it less frequently (e.g., every few chunks or based on a time interval). It helps keep your UI lightweight.

The HTML elements in the example above set up a progress bar to visualize the download progress and a preformatted text area to display the downloaded content. The progress-bar div serves as the container, and the progress div represents the actual progress of the download.

The JavaScript code updates the progress bar based on the received data length and total content length. The updateProgressBar function calculates the percentage of downloaded data and adjusts the width of the progress bar accordingly. The event listener triggers the fetchWithProgress function on page load, updating the progress bar and displaying the downloaded content in the output element.

Note: fetchWithProgress is defined in the previous snippet. In a real project, ensure it is in scope (e.g., via module imports, bundling, or a global script tag).

Professional Tips

  • Handle Binary Data: TextDecoder only works for text. Use response.blob() or response.arrayBuffer() for binary files.
  • Throttle Progress Updates: Rapid chunk reads can block the UI thread. Throttle or debounce the progress callback.
  • Memory Management: Avoid storing all chunks in an array for large files. Process them incrementally.
  • Content-Length Fallback: The header is often missing. Always provide a reliable fallbackSize.
  • Optimize User Feedback: Providing real-time feedback on download progress improves user satisfaction and can make your application feel more responsive.
  • Leverage Browser Capabilities: Different browsers may have varying support for advanced features. Test your implementation across multiple browsers to ensure compatibility.
  • Simplify When Possible: For downloads where streaming progress isn't strictly required, response.arrayBuffer() offers a simpler way to reconstruct data without manually concatenating chunks.

With these insights and examples, you are now equipped to implement the Fetch API with download progress tracking in your projects, offering users a seamless and informative experience.

Conclusion

Mastering the Fetch API involves understanding not only how to make basic requests but also how to handle more advanced scenarios such as tracking download progress. By using ReadableStreams, we can monitor and provide feedback on downloads, significantly enhancing the user experience. Implementing these techniques will ensure your applications are robust, user-friendly, and capable of handling large data transfers efficiently.

Practice

What are the key steps to track download progress using the Fetch API?

Dual-run preview — compare with live Symfony routes.