Javascript Fetch:下載進度

2023-02-17 10:57 更新

?fetch? 方法允許去跟蹤 下載 進度。

請注意:到目前為止,fetch 方法無法跟蹤 上傳 進度。對于這個目的,請使用 XMLHttpRequest,我們在后面章節(jié)會講到。

要跟蹤下載進度,我們可以使用 response.body 屬性。它是 ReadableStream —— 一個特殊的對象,它可以逐塊(chunk)提供 body。在 Streams API 規(guī)范中有對 ReadableStream 的詳細(xì)描述。

與 response.text(),response.json() 和其他方法不同,response.body 給予了對進度讀取的完全控制,我們可以隨時計算下載了多少。

這是從 response.body 讀取 response 的示例代碼:

// 代替 response.json() 以及其他方法
const reader = response.body.getReader();

// 在 body 下載時,一直為無限循環(huán)
while(true) {
  // 當(dāng)最后一塊下載完成時,done 值為 true
  // value 是塊字節(jié)的 Uint8Array
  const {done, value} = await reader.read();

  if (done) {
    break;
  }

  console.log(`Received ${value.length} bytes`)
}

await reader.read() 調(diào)用的結(jié)果是一個具有兩個屬性的對象:

  • ?done? —— 當(dāng)讀取完成時為 ?true?,否則為 ?false?。
  • ?value? —— 字節(jié)的類型化數(shù)組:?Uint8Array?。

請注意:

Streams API 還描述了如果使用 for await..of 循環(huán)異步迭代 ReadableStream,但是目前為止,它還未得到很好的支持(參見 瀏覽器問題),所以我們使用了 while 循環(huán)。

我們在循環(huán)中接收響應(yīng)塊(response chunk),直到加載完成,也就是:直到 done 為 true。

要將進度打印出來,我們只需要將每個接收到的片段 value 的長度(length)加到 counter 即可。

這是獲取響應(yīng),并在控制臺中記錄進度的完整工作示例,下面有更多說明:

// Step 1:啟動 fetch,并獲得一個 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:獲得總長度(length)
const contentLength = +response.headers.get('Content-Length');

// Step 3:讀取數(shù)據(jù)
let receivedLength = 0; // 當(dāng)前接收到了這么多字節(jié)
let chunks = []; // 接收到的二進制塊的數(shù)組(包括 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:將塊連接到單個 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:解碼成字符串
let result = new TextDecoder("utf-8").decode(chunksAll);

// 我們完成啦!
let commits = JSON.parse(result);
alert(commits[0].author.login);

讓我們一步步解釋下這個過程:

  1. 我們像往常一樣執(zhí)行 fetch,但不是調(diào)用 response.json(),而是獲得了一個流讀取器(stream reader)response.body.getReader()
  2. 請注意,我們不能同時使用這兩種方法來讀取相同的響應(yīng)。要么使用流讀取器,要么使用 reponse 方法來獲取結(jié)果。

  3. 在讀取數(shù)據(jù)之前,我們可以從 Content-Length header 中得到完整的響應(yīng)長度。
  4. 跨源請求中可能不存在這個 header(請參見 Fetch:跨源請求),并且從技術(shù)上講,服務(wù)器可以不設(shè)置它。但是通常情況下它都會在那里。

  5. 調(diào)用 await reader.read(),直到它完成。
  6. 我們將響應(yīng)塊收集到數(shù)組 chunks 中。這很重要,因為在使用完(consumed)響應(yīng)后,我們將無法使用 response.json() 或者其他方式(你可以試試,將會出現(xiàn) error)去“重新讀取”它。

  7. 最后,我們有了一個 chunks —— 一個 Uint8Array 字節(jié)塊數(shù)組。我們需要將這些塊合并成一個結(jié)果。但不幸的是,沒有單個方法可以將它們串聯(lián)起來,所以這里需要一些代碼來實現(xiàn):
    1. 我們創(chuàng)建 ?chunksAll = new Uint8Array(receivedLength)? —— 一個具有所有數(shù)據(jù)塊合并后的長度的同類型數(shù)組。
    2. 然后使用 ?.set(chunk, position)? 方法,從數(shù)組中一個個地復(fù)制這些 ?chunk?。
  8. 我們的結(jié)果現(xiàn)在儲存在 chunksAll 中。但它是一個字節(jié)數(shù)組,不是字符串。
  9. 要創(chuàng)建一個字符串,我們需要解析這些字節(jié)??梢允褂脙?nèi)建的 TextDecoder 對象完成。然后,我們可以 JSON.parse 它,如果有必要的話。

    如果我們需要的是二進制內(nèi)容而不是字符串呢?這更簡單。用下面這行代碼替換掉第 4 和第 5 步,這行代碼從所有塊創(chuàng)建一個 Blob

    let blob = new Blob(chunks);
    

最后,我們得到了結(jié)果(以字符串或 blob 的形式表示,什么方便就用什么),并在過程中對進度進行了跟蹤。

再強調(diào)一遍,這不能用于 上傳 過程(現(xiàn)在無法通過 fetch 獲?。?,僅用于 下載 過程。

另外,如果大小未知,我們應(yīng)該檢查循環(huán)中的 receivedLength,一旦達到一定的限制就將其中斷。這樣 chunks 就不會溢出內(nèi)存了。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號