## 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)