一文帶你徹底搞懂JS大文件分片上傳的實現(xiàn)
學習AbortController
AbortController 接口表示一個控制器對象,允許你根據(jù)需要中止一個或多個 Web 請求。
可以通過這個來實現(xiàn)取消或者中斷請求的功能。
axios.abort();底層實現(xiàn)就是這個。
認識了
fse = require('fs-extra')
概述
實現(xiàn)文件分片上傳的原理就是通過將文件的ArrayBlob形式,通過file(blob)的方法slice,來實現(xiàn)將文件拆成幾個部分,然后排上順序傳到服務端,最后傳完了調(diào)用服務端的merge合并接口,服務端將文件合并。
服務端實現(xiàn)原理,上傳前創(chuàng)建文件夾,命名注意了,可以參考給到的代碼。然后將獲得的文件通過fse的寫入方法,寫入到我們創(chuàng)建的文件夾中,并且有相應的排序。最后我們通過合并方法將文件合并到一個文件。
詳細學習
Client端
getChunkListAndFileMd5函數(shù)
這個函數(shù)用來創(chuàng)建分片數(shù)組,以及生成Hash簽名。
創(chuàng)建分片列表數(shù)組,我們使用的方法是file.prototype.slice當然這里我們?yōu)榱私鉀Q兼容性問題,我們使用的代碼是:
export function getBlobSlice() { return (File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice); }
創(chuàng)建分片:getBlobSlice.call(file,start,end)。這里start、end分別是起始位置和終點位置,我們確認好分片大小就可以計算start和end。
好,那么我們開始了,我們定義size是 5M 代碼如下
const DEFAULT_CHUNK_SIZE = 5 * 1024 * 1024;
我們定義一個對象保存默認配置:
const DEFAULT_OPTIONS = { chunkSize: DEFAULT_CHUNK_SIZE, };
我們來定義分片的編號,初始值肯定是 0。我們定義chunkSize來保存我們上邊定義的常量分片大小。
let currentChunk = 0; const chunkSize = this.fileUploaderClientOptions.chunkSize;
接下來我們需要計算需要多少個分片,很簡單:文件總的大小/每個分片的大小,最終結果可能是個小數(shù),但是為了保證分片的完整肯定是向上取整。
const chunks = Math.ceil(file.size / chunkSize);
定義一個函數(shù)來加載分片,代碼如下:
function loadNextChunk() { const start = currentChunk * chunkSize; const end = start + chunkSize >= file.size ? file.size : start + chunkSize; const chunk = blobSlice.call(file, start, end); chunkList.push(chunk); fileReader.readAsArrayBuffer(chunk); }
拆解這個函數(shù):
1.計算start位置,這個不難理解。
2.計算end位置,這里需要判斷一下,主要是針對兩種情況:
文件尺寸很小,小于分片大小,直接取文件的尺寸。
最后一個切片,大小可能沒有切片尺寸大,我們直接取文件大小的位置。
3.調(diào)用我們上邊說的函數(shù)來實現(xiàn)分片,并且獲取當前的分片。
4.將當前的分片push到我們的數(shù)組中。
5.調(diào)用fileReader.readAsArrayBuffer方法來讀取分片。(其實當前的分片是一個blob實例,我們通過這個方法可以讀取到里邊的內(nèi)容)。
接下來我們來看看fileReader是怎么回事。
認識FileReader
不懂fileReader的可以先看看文檔,了解下里邊的方法。
FileReader 對象允許 Web 應用程序異步讀取存儲在用戶計算機上的文件(或原始數(shù)據(jù)緩沖區(qū))的內(nèi)容,使用 File 或 Blob 對象指定要讀取的文件或數(shù)據(jù)。
其中 File 對象可以是
- 來自用戶在一個 元素上選擇文件后返回的FileList對象
- 也可以來自拖放操作生成的 DataTransfer對象,
- 還可以是來自在一個HTMLCanvasElement上執(zhí)行mozGetAsFile()方法后返回結果。
FileReader可以在Web Worker中使用。 (重要,可以很大成都解決性能問題)
這里因為用到了FileReader的方法,所以重點講講這個對象的方法。
- FileReader.abort(); 中止讀取操作。
- FileReader.readAsArrayBuffer(); 開始讀取指定的 Blob中的內(nèi)容,一旦完成,result 屬性中保存的將是被讀取文件的 ArrayBuffer數(shù)據(jù)對象。
- FileReader.readAsDataURL(); 開始讀取指定的Blob中的內(nèi)容。一旦完成,result屬性中將包含一個data: URL 格式的 Base64 字符串以表示所讀取文件的內(nèi)容。
- FileReader.readAsText();開始讀取指定的Blob中的內(nèi)容。一旦完成,result屬性中將包含一個字符串以表示所讀取的文件內(nèi)容。
我們目前需要讀取分片的內(nèi)容,并且需要ArrayBuffer的格式,所以我們創(chuàng)建一個FileReader對象實例,并且使用readAsArrayBuffer方法來讀取文件,通過onload事件來監(jiān)聽,獲取最終結果。代碼如下:
const fileReader = new FileReader(); fileReader.onload = function (e) { // 我們讀取到的結果是 e.target.result } fileReader.onerror = function (e) { // 這里表示讀取失敗 }
上邊loadNextChunk函數(shù)中調(diào)用了這個方法。
fileReader.readAsArrayBuffer(chunk);
使用md5來實現(xiàn)簽名
用到了 spark-md5 這個三方庫。主要使用的方法是生成md5的編碼。
npm install --save spark-md5
這個就是一個處理分片的一個庫。給出了方法:
const spark = new SparkMD5.ArrayBuffer();
將分片 v 添加到對象中
spark.append(v)
最終結果返回
const result = spark.end()
uploadFile函數(shù)
這個函數(shù)用來上傳文件的函數(shù)。
首先我們要獲取上邊我們函數(shù)得到的值 md5、chunkList。
const { md5, chunkList } = yield this.getChunkListAndFileMd5(file);
上傳文件前,需要調(diào)用接口來初始化文件分片上傳,這里我們其實就是調(diào)用初始化文件分片上傳的接口requestOptions.initFilePartUploadFunc。
yield requestOptions.initFilePartUploadFunc();
這個接口在后端的實現(xiàn)其實就是創(chuàng)建好一個文件夾用來保存分片,這里就不多說了,之后在學習Server代碼的時候我們細講。
接下來就是開始上傳我們的分片了,上傳方法requestOptions.uploadPartFileFunc:
for (let index = 0; index < chunkList.length; index++) { try { yield requestOptions.uploadPartFileFunc(chunkList[index], index); } catch (e) { console.warn(`${index} part upload failed`); retryList.push(index); } }
注意了:我們不能保證全部順利上傳,如果中間出現(xiàn)問題中斷了上傳等問題,我們?nèi)绾翁幚恚?/p>
這里我們使用retryTimes來獲取需要重新上傳的列表。
for (let retry = 0; retry < requestOptions.retryTimes; retry++) { if (retryList.length > 0) { console.log(`retry start, times: ${retry}`); for (let a = 0; a < retryList.length; a++) { const blobIndex = retryList[a]; try { yield requestOptions.uploadPartFileFunc(chunkList[blobIndex], blobIndex); retryList.splice(a, 1); } catch (e) { console.warn(`${blobIndex} part retry upload failed, times: ${retry}`); } } } }
最后我們調(diào)用上傳結束的接口requestOptions.finishFilePartUploadFunc(md5),其實這個接口主要是通知服務端,分片都上傳完了,服務端可以進行文件合并了,最終將分片合并成一個文件。
if (retryList.length === 0) { return yield requestOptions.finishFilePartUploadFunc(md5); } else { throw Error(`upload failed, some chunks upload failed: ${JSON.stringify(retryList)}`); }
至此,客戶端的操作完畢!
以上就是一文帶你徹底搞懂JS大文件分片上傳的實現(xiàn)的詳細內(nèi)容,更多關于JS大文件分片上傳的資料請關注腳本之家其它相關文章!
相關文章
關于js中removeEventListener取消事件監(jiān)聽的坑
許多入前端不久的人都會遇到removeEventListener無法清除監(jiān)聽的情況,下面這篇文章主要給大家介紹了關于js中removeEventListener取消事件監(jiān)聽的坑,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2022-09-09重學 JS:為啥 await 不能用在 forEach 中詳解
這篇文章主要介紹了重學 JS:為啥 await 不能用在 forEach 中,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-04-04解決微信授權成功后點擊按返回鍵出現(xiàn)空白頁和報錯的問題
這篇文章主要介紹了解決微信授權成功后點擊按返回鍵出現(xiàn)空白頁和報錯的問題,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-06-06DOM操作原生js 的bug,使用jQuery 可以消除的解決方法
下面小編就為大家?guī)硪黄狣OM操作原生js 的bug,使用jQuery 可以消除的解決方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-09-09