AJAX 請求時獲取加載進度

loading
Photo by Mike van den Bos on Unsplash

Progress Events

Progress events 用於描述資源加載的進度,主要由 AJAX 請求, <img>, <audio>, <video>, <style>, <link>…等外部資源的加載觸發。

主要包含以下幾種事件:

  • abort: 外部資源中止加載時(比如使用者取消)觸發
  • error: 由於錯誤導致外部資源無法加載時觸發
  • load: 外部資源加載成功時觸發
  • loadstart: 外部資源開始加載時觸發
  • loadend: 外部資源停止加載時觸發, 發生順序在 error, abort, load…等事件的後面
  • progress: 外部資源加載過程中不斷觸發
  • timeout: 加載過時時觸發

比如下面這個例子:

image.addEventListener('load', (e) => {
  image.classList.add('finished')
})

image.addEventListener('error', (e) => {
  image.style.display = 'none'
})

XHR 獲取當前請求進度

前面有提到 Progress event 其中包含 progress 事件是在外部資源加載過程中不斷觸發,所以我們要計算請求的當前進度就需要透過監聽 progress 事件來實現。

progress 事件的返回值是一個物件,物件中包括 loadedtotal 這兩個值,loaded 代表當前請求的 byte 數,total 則是總共的 byte 數。

物件中還有一個 lengthComputable,如果 lengthComputable 為 true 代表該資源有可計算的長度,此時才能計算請求進度,如果為 false 就無法計算。

const xhr = new XMLHttpRequest()
xhr.addEventListener('progress', (e) => {
  if (e.lengthComputable) {
    console.log(e.loaded, e.total)
    console.log('進度(%)', (e.loaded / e.total) * 100 + '%')
  } else {
    console.log('無法計算進度')
  }
})

簡單的例子如下:

var xhr = new XMLHttpRequest();

xhr.addEventListener('progress', (e) => {
  console.log(e);
  if (e.lengthComputable) {
    console.log(e.loaded, e.total);
    console.log('進度(%)', (e.loaded / e.total) * 100 + '%');
  } else {
    console.log('無法計算進度');
  }
});

xhr.open('GET', 'https://pokeapi.co/api/v2/pokemon/ditto');
xhr.send();

而如果要監聽上傳進度,只需要在 xhr.addEventListener 中間加上 upload 就可以,其餘寫法都一樣:

xhr.upload.addEventListener('progress', (e) => {
    if (e.lengthComputable) {
      console.log(e.loaded, e.total)
      console.log('進度(%)', (e.loaded / e.total) * 100 + '%')
    } else {
      console.log('無法計算進度')
    }
})

axios

axios 本身有提供 onDownloadProgress 方法來獲取 progress event 物件,就不需要再自己寫事件監聽了:

axios({
  method: "get",
  url: "http://api.demo.com/todos",
  onDownloadProgress(progressEvent) {
    console.log(progressEvent)
  }
})

fetch 獲取當前請求進度

fetch 無法直接獲取請求進度,不過 fetch response 裡有一個 body,這個 body 是一個可讀流,可以藉由可讀流的 getReader() 方法拿到一個讀取器(reader),將讀取器中的 value 一直累加就是當前已加載的資料量(byte),直到 done 為 true 時代表請求完成。

至於總資料量,直接從 response 獲取 content-length 即可。

<div id="app">
  <input type="button" name="btn" value="Submit" />
</div>
const button = document.getElementsByName('btn')[0]

button.addEventListener('click', () => getProgress())

const getProgress = async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/comments')
  const total = +response.headers.get('content-length')
  const reader = response.body.getReader()
  let loaded = 0
  while (1) {
    const { done, value } = await reader.read()
    if (done) break
    loaded += value.length
    console.log(loaded, total)
  }
}

上傳進度

fetch 目前無法計算上傳進度

Progress event 的 total 總是回傳 0

這是因為 response headers 沒有 Content-Length 而導致,可能是因為檔案被分塊發送或者使用gzip壓縮(Content-Encoding: gzip)。

解決方法就只有請後端在 response headers 加上 Content-Length 了。

Reference

留言

目前沒有留言。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *