fetch
方法允許追蹤下載進度。
請注意:目前 fetch
無法追蹤上傳進度。為此,請使用 XMLHttpRequest,我們稍後會介紹。
若要追蹤下載進度,我們可以使用 response.body
屬性。它是一個 ReadableStream
,一個特殊的物件,會逐塊提供主體,因為它會出現。可讀取串流在 串流 API 規範中進行說明。
與 response.text()
、response.json()
和其他方法不同,response.body
可以完全控制讀取程序,我們可以隨時計算消耗了多少。
以下是從 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`)
}
await reader.read()
呼叫的結果是一個具有兩個屬性的物件
done
– 讀取完成時為true
,否則為false
。value
– 位元組的類型化陣列:Uint8Array
。
Streams API 也描述了使用 for await..of
迴圈對 ReadableStream
進行非同步反覆運算,但它尚未廣泛支援(請參閱 瀏覽器問題),因此我們使用 while
迴圈。
我們在迴圈中接收回應區塊,直到載入完成,也就是說:直到 done
變成 true
。
若要記錄進度,我們只需要對每個接收到的片段 value
將其長度新增到計數器中。
以下是完整的實際範例,它會取得回應並在主控台中記錄進度,後續會有更多說明
// 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);
讓我們逐步說明
-
我們照常執行
fetch
,但我們不會呼叫response.json()
,而是取得串流讀取器response.body.getReader()
。請注意,我們無法使用這兩種方法來讀取相同的回應:請使用讀取器或回應方法來取得結果。
-
在讀取之前,我們可以從
Content-Length
標頭中找出完整的回應長度。對於跨來源要求(請參閱章節 Fetch:跨來源要求),它可能不存在,而且從技術上來說,伺服器不必設定它。但通常它會存在。
-
呼叫
await reader.read()
直到它完成。我們在陣列
chunks
中收集回應區塊。這很重要,因為在回應被使用後,我們將無法使用response.json()
或其他方式「重新讀取」它(您可以試試看,會出現錯誤)。 -
最後,我們有
chunks
– 一個Uint8Array
位元組區塊陣列。我們需要將它們合併成單一結果。很遺憾地,沒有單一方法可以串接它們,因此有一些程式碼可以做到這一點- 我們建立
chunksAll = new Uint8Array(receivedLength)
– 一個具有合併長度的相同類型陣列。 - 然後使用
.set(chunk, position)
方法將每個chunk
一個接一個地複製到其中。
- 我們建立
-
我們在
chunksAll
中有結果。不過它是一個位元組陣列,而不是字串。若要建立字串,我們需要詮釋這些位元組。內建的 TextDecoder 正好可以做到這一點。然後我們可以
JSON.parse
它(如果需要的話)。如果我們需要二進位內容而不是字串,那就更簡單了。將步驟 4 和 5 替換為單一行,從所有區塊建立一個
Blob
let blob = new Blob(chunks);
最後,我們有了結果(作為字串或 blob,無論哪一種都方便),並在過程中追蹤進度。
再次請注意,這不是用於上傳進度(現在無法使用fetch
),僅用於下載進度。
此外,如果大小未知,我們應該在迴圈中檢查receivedLength
,並在達到某個限制後中斷迴圈。這樣chunks
才不會溢位記憶體。
留言
<code>
標籤,若要插入多行程式碼,請將程式碼包覆在<pre>
標籤中,若要插入超過 10 行的程式碼,請使用沙盒 (plnkr、jsbin、codepen…)