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 事件的返回值是一個物件,物件中包括 loaded
和 total
這兩個值,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
了。