Lesson guide
Learning objectives
- Explain the main purpose of Fetch: Download progress in JavaScript.
- Identify the syntax, APIs, or concepts introduced in this lesson.
- Use the examples to predict how JavaScript will behave before you run similar code.
- Connect this topic to nearby lessons in Network requests.
Real-world context
This lesson matters when you need to recognize where Fetch: Download progress fits into real JavaScript programs. The method allows to track download progress.
Key ideas
- Fetch: Download progress is part of the Network requests chapter, so it builds on the surrounding concepts rather than standing alone.
- Read each code example in two passes: first for the result, then for the rule that explains the result.
- When a section compares similar features, focus on the condition that makes you choose one feature over another.
Key terms
- Fetch: Download progress
- Fetch
- Download
- progress
- Network requests
The fetch method allows to track download progress.
Please note: there’s currently no way for fetch to track upload progress. For that purpose, please use XMLHttpRequest, we’ll cover it later.
To track download progress, we can use response.body property. It’s a ReadableStream – a special object that provides body chunk-by-chunk, as it comes. Readable streams are described in the Streams API specification.
Unlike response.text(), response.json() and other methods, response.body gives full control over the reading process, and we can count how much is consumed at any moment.
Here’s the sketch of code that reads the response from response.body:
// instead of response.json() and other methods const reader = response.body.getReader(); // infinite loop while the body is downloading while(true) { // done is true for the last chunk // value is Uint8Array of the chunk bytes const {done, value} = await reader.read(); if (done) { break; } console.log(`Received ${value.length} bytes`) }
The result of await reader.read() call is an object with two properties:
done–truewhen the reading is complete, otherwisefalse.value– a typed array of bytes:Uint8Array.
Please note:
Streams API also describes asynchronous iteration over ReadableStream with for await..of loop, but it’s not yet widely supported (see browser issues), so we use while loop.
We receive response chunks in the loop, until the loading finishes, that is: until done becomes true.
To log the progress, we just need for every received fragment value to add its length to the counter.
Here’s the full working example that gets the response and logs the progress in console, more explanations to follow:
// Step 1: start the fetch and obtain a reader let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits?per_page=100'); const reader = response.body.getReader(); // Step 2: get total length const contentLength = +response.headers.get('Content-Length'); // Step 3: read the data let receivedLength = 0; // received that many bytes at the moment let chunks = []; // array of received binary chunks (comprises the body) while(true) { const {done, value} = await reader.read(); if (done) { break; } chunks.push(value); receivedLength += value.length; console.log(`Received ${receivedLength} of ${contentLength}`) } // Step 4: concatenate chunks into single Uint8Array let chunksAll = new Uint8Array(receivedLength); // (4.1) let position = 0; for(let chunk of chunks) { chunksAll.set(chunk, position); // (4.2) position += chunk.length; } // Step 5: decode into a string let result = new TextDecoder("utf-8").decode(chunksAll); // We're done! let commits = JSON.parse(result); alert(commits[0].author.login);
Let’s explain that step-by-step:
- We perform
fetchas usual, but instead of callingresponse.json(), we obtain a stream readerresponse.body.getReader().
Please note, we can’t use both these methods to read the same response: either use a reader or a response method to get the result.
- Prior to reading, we can figure out the full response length from the
Content-Lengthheader.
It may be absent for cross-origin requests (see chapter Fetch: Cross-Origin Requests) and, well, technically a server doesn’t have to set it. But usually it’s at place.
- Call
await reader.read()until it’s done.
We gather response chunks in the array chunks. That’s important, because after the response is consumed, we won’t be able to “re-read” it using response.json() or another way (you can try, there’ll be an error).
- At the end, we have
chunks– an array ofUint8Arraybyte chunks. We need to join them into a single result. Unfortunately, there’s no single method that concatenates those, so there’s some code to do that: - We create
chunksAll = new Uint8Array(receivedLength)– a same-typed array with the combined length. - Then use
.set(chunk, position)method to copy eachchunkone after another in it. - We have the result in
chunksAll. It’s a byte array though, not a string.
To create a string, we need to interpret these bytes. The built-in TextDecoder does exactly that. Then we can JSON.parse it, if necessary.
What if we need binary content instead of a string? That’s even simpler. Replace steps 4 and 5 with a single line that creates a Blob from all chunks:
let blob = new Blob(chunks);
At the end we have the result (as a string or a blob, whatever is convenient), and progress-tracking in the process.
Once again, please note, that’s not for upload progress (no way now with fetch), only for download progress.
Also, if the size is unknown, we should check receivedLength in the loop and break it once it reaches a certain limit. So that the chunks won’t overflow the memory.
Common mistakes
- Skipping the small examples and then missing the exact rule that Fetch: Download progress depends on.
- Copying code without changing one value at a time to see which part controls the result.
- Treating similar-looking syntax or APIs as interchangeable before checking their edge cases.
Summary
- Fetch: Download progress gives you one more piece of the JavaScript mental model.
- The examples in this lesson are the quickest way to check whether the rule is clear.
- Revisit the key terms when you meet the same idea in later chapters.
Predict
Before running this download-progress model, predict the five output lines. It reads a response stream chunk by chunk, counts progress from Content-Length, concatenates chunks, and decodes the final bytes.
Reveal explanation
The output is progress:5/13, progress:11/13, progress:13/13, text:Hello, world!, and chunks:3. Download progress uses response.body.getReader() instead of response.text() or response.json(), because the stream exposes body bytes as they arrive. Each Uint8Array chunk increases the received byte count. After a stream is consumed, code must join the chunks itself, commonly with Uint8Array#set, and then decode the complete byte array with TextDecoder or create a Blob for binary content. This tracks download progress only; fetch still does not expose upload progress.
Try it
Remove the Content-Length header, then predict what progress information remains possible and what denominator disappears.
Practice
- Rewrite one example from this lesson without looking at the original, then run it and compare the result.
- Change one input, operator, method call, or option in a code sample and predict what will happen before running it.
- Explain Fetch: Download progress in your own words as if you were reviewing it with another learner.
Keep learning
Continue with Fetch: Abort when you are ready for the next lesson.