Download Progress with JavaScript’s Fetch Function
Last updated: January 6, 2023.
You can track download progress using JavaScript’s fetch function by reading the body of the response object you receive from a request.
By reading this as a stream, you can access information about how many bytes have been downloaded so far. With this information, you can update a download progress indicator (text or progress bar)
As bytes are downloaded, you pass these into a new ReadableStream
object on a Response
object. This can then be read in the using the regular fetch methods (response.json()
, response.blob()
, response.text()
) after the download is complete.
fetch('https://picsum.photos/400/400')
.then(response => {
const contentLength = response.headers.get('content-length');
// Gets length in bytes (must be provided by server)
let loaded = 0;
// Will be used to track loading
return new Response(
new ReadableStream({
// Creates new readable stream on the new response object
start(controller) {
// Controller has methods on that allow the new stream to be constructed
const reader = response.body.getReader();
// Creates a new reader to read the body of the fetched resources
read();
// Fires function below that starts reading
function read() {
reader.read()
.then((progressEvent) => {
// Starts reading, when there is progress this function will fire
if (progressEvent.done) {
controller.close();
return;
// Will finish constructing new stream if reading fetched of resource is complete
}
loaded += progressEvent.value.byteLength;
// Increase value of 'loaded' by latest reading of fetched resource
console.log(Math.round(loaded/contentLength*100)+'%');
// Displays progress via console log as %
controller.enqueue(progressEvent.value);
// Add newly read data to the new readable stream
read();
// Runs function again to continue reading and creating new stream
})
}
}
})
);
})
.then(response => response.blob()) // Read new readable stream to blob
.then(blob => {
new Image().src = URL.createObjectURL(blob);
document.body.appendChild(img);
// Create new URL to blob image, set as src of image and append to DOM
})
And below is an example of using the data being read from a fetched resource to update a CSS progress bar.
The bar advances by updating the width
of the progress-bar-fill
. This progressively spans the width of its parent element, progress-bar
:
<style>
body {
font-family: Arial, Helvetica, sans-serif;
}
/* CSS Progress bar */
.wrapper {
width: 600px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin: 0px auto;
margin-top: 30vh;
}
.progress-bar {
width: 100%;
background-color: whitesmoke;
padding: 2px;
}
.progress-bar-fill {
display: block;
height: 40px;
background-color: #65ef88;
border-radius: 3px;
width: 0%; /* Starts at 0%, updates with download progress */
transition: width 30ms ease-in-out;
}
.progress-text {
font-size: 1.5rem;
padding: 10px;
}
img {
display: block;
margin: 0px auto;
}
</style>
<body>
<div class="wrapper">
<div class="progress-bar">
<span class="progress-bar-fill"></span>
</div>
<span class="progress-text">Download starting...</span>
</div>
</body>
<script>
const fill = document.querySelector('.progress-bar-fill');
const text = document.querySelector('.progress-text');
fetch('https://picsum.photos/400/400')
.then(response => {
const contentLength = response.headers.get('content-length');
let loaded = 0;
return new Response(
new ReadableStream({
start(controller) {
const reader = response.body.getReader();
read();
function read() {
reader.read()
.then((progressEvent) => {
if (progressEvent.done) {
controller.close();
return;
}
loaded += progressEvent.value.byteLength;
const percentageComplete = Math.round(loaded/contentLength*100)+'%';
fill.style.width = percentageComplete;
text.innerText = percentageComplete;
// Calculates % complete and updates progress bar and print text to DOM
controller.enqueue(progressEvent.value);
read();
})
}
}
})
);
})
.then(response => response.blob())
.then(blob => {
const img = new Image()
img.src = URL.createObjectURL(blob);
document.body.appendChild(img);
})
</script>
Related posts
- Using fetch to make GET, POST, PUT and DELETE requests
- Using Fetch with async/await
- Upload progress bar using XHR (Fetch alternative)
- Image URL to Blob in JavaScript
- Download Progress Bar with Axios
- Create an upload progress bar with Axios