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 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 reponse 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.
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 CORS) 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 usingresponse.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 create
-
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.parseit, 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
Blobfrom 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.
ëê¸
<code>í그를, ì¬ë¬ ì¤ë¡ 구ì±ë ì½ë를 ì½ì íê³ ì¶ë¤ë©´<pre>í그를 ì´ì©íì¸ì. 10ì¤ ì´ìì ì½ëë plnkr, JSBin, codepen ë±ì ìëë°ì¤ë¥¼ ì¬ì©íì¸ì.