## Progress Events
Events progress 用於描述資源加載的進度,主要由 AJAX 請求, `<img>`, `<audio>`, `<video>`, `<style>`, `<link>`...等外部資源的加載觸發。
主要包含以下幾種事件:
- **abort**: 外部資源中止加載時(比如使用者取消)觸發
- **error**: 由於錯誤導致外部資源無法加載時觸發
- **load**: 外部資源加載成功時觸發
- **loadstart**: 外部資源開始加載時觸發
- **loadend**: 外部資源停止加載時觸發, 發生順序在 error, abort, load...等事件的後面
- **progress**: 外部資源加載過程中不斷觸發
- **timeout**: 加載過時時觸發
比如下面這個例子:
```js
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 就無法計算。
```js
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('無法計算進度')
}
})
```
簡單的例子如下:
```js
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` 就可以,其餘寫法都一樣:
```js
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 物件,就不需要再自己寫事件監聽了:
```js=
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` 即可。
```html
<div id="app">
<input type="button" name="btn" value="Submit" />
</div>
```
```js
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
- [进度事件](https://wangdoc.com/javascript/events/progress)
- [AJAX进度监控【渡一教育】](https://www.bilibili.com/video/BV1Ku4y147YF)