日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
JavaScript 中如何實現(xiàn)大文件并發(fā)上傳?

在 JavaScript 中如何實現(xiàn)并發(fā)控制? 這篇文章中,阿寶哥詳細(xì)分析了 async-pool 這個庫如何利用 Promise.all 和 Promise.race 函數(shù)實現(xiàn)異步任務(wù)的并發(fā)控制。之后,阿寶哥通過 JavaScript 中如何實現(xiàn)大文件并行下載? 這篇文章介紹了 async-pool 這個庫的實際應(yīng)用。

網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)建站!專注于網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、微信小程序定制開發(fā)、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了魚臺免費(fèi)建站歡迎大家使用!

本文將介紹如何利用 async-pool 這個庫提供的 asyncPool 函數(shù)來實現(xiàn)大文件的并發(fā)上傳。相信有些小伙伴已經(jīng)了解大文件上傳的解決方案,在上傳大文件時,為了提高上傳的效率,我們一般會使用 Blob.slice 方法對大文件按照指定的大小進(jìn)行切割,然后通過多線程進(jìn)行分塊上傳,等所有分塊都成功上傳后,再通知服務(wù)端進(jìn)行分塊合并。

看完上圖相信你對大文件上傳的方案,已經(jīng)有了一定的了解。接下來,我們先來介紹 Blob 和 File 對象。

一、Blob 和 File 對象

1.1 Blob 對象

Blob(Binary Large Object)表示二進(jìn)制類型的大對象。在數(shù)據(jù)庫管理系統(tǒng)中,將二進(jìn)制數(shù)據(jù)存儲為一個單一個體的集合。Blob 通常是影像、聲音或多媒體文件。在 JavaScript 中 Blob 類型的對象表示不可變的類似文件對象的原始數(shù)據(jù)。 為了更直觀的感受 Blob 對象,我們先來使用 Blob 構(gòu)造函數(shù),創(chuàng)建一個 myBlob 對象,具體如下圖所示:

如你所見,myBlob 對象含有兩個屬性:size 和 type。其中 size 屬性用于表示數(shù)據(jù)的大小(以字節(jié)為單位),type 是 MIME 類型的字符串。Blob 由一個可選的字符串 type(通常是 MIME 類型)和 blobParts 組成:

Blob 表示的不一定是 JavaScript 原生格式的數(shù)據(jù)。比如 File 接口基于 Blob,繼承了 Blob 的功能并將其擴(kuò)展使其支持用戶系統(tǒng)上的文件。

1.2 File 對象

通常情況下, File 對象是來自用戶在一個 元素上選擇文件后返回的 FileList 對象,也可以是來自由拖放操作生成的 DataTransfer 對象,或者來自 HTMLCanvasElement 上的 mozGetAsFile() API。

File 對象是特殊類型的 Blob,且可以用在任意的 Blob 類型的上下文中。比如說 FileReader、URL.createObjectURL() 及 XMLHttpRequest.send() 都能處理 Blob 和 File。在大文件上傳的場景中,我們將使用 Blob.slice 方法對大文件按照指定的大小進(jìn)行切割,然后對分塊進(jìn)行并行上傳。接下來,我們來看一下具體如何實現(xiàn)大文件上傳。

二、如何實現(xiàn)大文件上傳

為了讓大家能夠更好地理解后面的內(nèi)容,我們先來看一下整體的流程圖:

了解完大文件上傳的流程之后,我們先來定義上述流程中涉及的一些輔助函數(shù)。

2.1 定義輔助函數(shù)

2.1.1 定義 calcFileMD5 函數(shù)

顧名思義 calcFileMD5 函數(shù),用于計算文件的 MD5 值(數(shù)字指紋)。在該函數(shù)中,我們使用 FileReader API 分塊讀取文件的內(nèi)容,然后通過 spark-md5 這個庫提供的方法來計算文件的 MD5 值。

 
 
 
 
  1. function calcFileMD5(file) { 
  2.   return new Promise((resolve, reject) => { 
  3.     let chunkSize = 2097152, // 2M 
  4.       chunks = Math.ceil(file.size / chunkSize), 
  5.       currentChunk = 0, 
  6.       spark = new SparkMD5.ArrayBuffer(), 
  7.       fileReader = new FileReader(); 
  8.  
  9.       fileReader.onload = (e) => { 
  10.         spark.append(e.target.result); 
  11.         currentChunk++; 
  12.         if (currentChunk < chunks) { 
  13.           loadNext(); 
  14.         } else { 
  15.           resolve(spark.end()); 
  16.         } 
  17.       }; 
  18.  
  19.       fileReader.onerror = (e) => { 
  20.         reject(fileReader.error); 
  21.         reader.abort(); 
  22.       }; 
  23.  
  24.       function loadNext() { 
  25.         let start = currentChunk * chunkSize, 
  26.           end = start + chunkSize >= file.size ? file.size : start + chunkSize; 
  27.         fileReader.readAsArrayBuffer(file.slice(start, end)); 
  28.       } 
  29.       loadNext(); 
  30.   }); 

2.1.2 定義 asyncPool 函數(shù)

在 JavaScript 中如何實現(xiàn)并發(fā)控制? 這篇文章中,我們介紹了 asyncPool 函數(shù),它用于實現(xiàn)異步任務(wù)的并發(fā)控制。該函數(shù)接收 3 個參數(shù):

  • poolLimit(數(shù)字類型):表示限制的并發(fā)數(shù);
  • array(數(shù)組類型):表示任務(wù)數(shù)組;
  • iteratorFn(函數(shù)類型):表示迭代函數(shù),用于實現(xiàn)對每個任務(wù)項進(jìn)行處理,該函數(shù)會返回一個 Promise 對象或異步函數(shù)。
 
 
 
 
  1. async function asyncPool(poolLimit, array, iteratorFn) { 
  2.   const ret = []; // 存儲所有的異步任務(wù) 
  3.   const executing = []; // 存儲正在執(zhí)行的異步任務(wù) 
  4.   for (const item of array) { 
  5.     // 調(diào)用iteratorFn函數(shù)創(chuàng)建異步任務(wù) 
  6.     const p = Promise.resolve().then(() => iteratorFn(item, array)); 
  7.     ret.push(p); // 保存新的異步任務(wù) 
  8.  
  9.     // 當(dāng)poolLimit值小于或等于總?cè)蝿?wù)個數(shù)時,進(jìn)行并發(fā)控制 
  10.     if (poolLimit <= array.length) { 
  11.       // 當(dāng)任務(wù)完成后,從正在執(zhí)行的任務(wù)數(shù)組中移除已完成的任務(wù) 
  12.       const e = p.then(() => executing.splice(executing.indexOf(e), 1)); 
  13.       executing.push(e); // 保存正在執(zhí)行的異步任務(wù) 
  14.       if (executing.length >= poolLimit) { 
  15.         await Promise.race(executing); // 等待較快的任務(wù)執(zhí)行完成 
  16.       } 
  17.     } 
  18.   } 
  19.   return Promise.all(ret); 

2.1.3 定義 checkFileExist 函數(shù)

checkFileExist 函數(shù)用于檢測文件是否已經(jīng)上傳過了,如果已存在則秒傳,否則返回已上傳的分塊 ID 列表:

 
 
 
 
  1. function checkFileExist(url, name, md5) { 
  2.   return request.get(url, { 
  3.     params: { 
  4.       name, 
  5.       md5, 
  6.     }, 
  7.   }).then((response) => response.data); 

在 checkFileExist 函數(shù)中使用到的 request 對象是 Axios 實例,通過 axios.create方法來創(chuàng)建:

 
 
 
 
  1. const request = axios.create({ 
  2.   baseURL: "http://localhost:3000/upload", 
  3.   timeout: 10000, 
  4. }); 

有了 request 對象之后,我們就可以輕易地發(fā)送 HTTP 請求。在 checkFileExist 函數(shù)內(nèi)部,我們會發(fā)起一個 GET 請求,同時攜帶的查詢參數(shù)是文件名(name)和文件的 MD5 值。

2.1.4 定義 upload 函數(shù)

當(dāng)調(diào)用 checkFileExist 函數(shù)之后,如果發(fā)現(xiàn)文件尚未上傳或者只上傳完部分分塊的話,就會繼續(xù)調(diào)用 upload 函數(shù)來執(zhí)行上傳任務(wù)。在 upload 函數(shù)內(nèi),我們使用了前面介紹的 asyncPool 函數(shù)來實現(xiàn)異步任務(wù)的并發(fā)控制,具體如下所示:

 
 
 
 
  1. function upload({  
  2.   url, file, fileMd5,  
  3.   fileSize, chunkSize, chunkIds, 
  4.   poolLimit = 1, 
  5. }) { 
  6.   const chunks = typeof chunkSize === "number" ? Math.ceil(fileSize / chunkSize) : 1; 
  7.   return asyncPool(poolLimit, [...new Array(chunks).keys()], (i) => { 
  8.     if (chunkIds.indexOf(i + "") !== -1) { // 已上傳的分塊直接跳過 
  9.       return Promise.resolve(); 
  10.     } 
  11.     let start = i * chunkSize; 
  12.     let end = i + 1 == chunks ? fileSize : (i + 1) * chunkSize; 
  13.     const chunk = file.slice(start, end); // 對文件進(jìn)行切割 
  14.     return uploadChunk({ 
  15.       url, 
  16.       chunk, 
  17.       chunkIndex: i, 
  18.       fileMd5, 
  19.       fileName: file.name, 
  20.     }); 
  21.   }); 

對于切割完的文件塊,會通過 uploadChunk 函數(shù),來執(zhí)行實際的上傳操作:

 
 
 
 
  1. function uploadChunk({ url, chunk, chunkIndex, fileMd5, fileName }) { 
  2.   let formData = new FormData(); 
  3.   formData.set("file", chunk, fileMd5 + "-" + chunkIndex); 
  4.   formData.set("name", fileName); 
  5.   formData.set("timestamp", Date.now()); 
  6.   return request.post(url, formData); 

2.1.5 定義 concatFiles 函數(shù)

當(dāng)所有分塊都上傳完成之后,我們需要通知服務(wù)端執(zhí)行分塊合并操作,這里我們定義了 concatFiles 函數(shù)來實現(xiàn)該功能:

 
 
 
 
  1. function concatFiles(url, name, md5) { 
  2.   return request.get(url, { 
  3.     params: { 
  4.       name, 
  5.       md5, 
  6.     }, 
  7.   }); 

2.1.6 定義 uploadFile 函數(shù)

在前面已定義輔助函數(shù)的基礎(chǔ)上,我們就可以根據(jù)大文件上傳的整體流程圖來實現(xiàn)一個 uploadFile 函數(shù):

 
 
 
 
  1. async function uploadFile() { 
  2.   if (!uploadFileEle.files.length) return; 
  3.   const file = uploadFileEle.files[0]; // 獲取待上傳的文件 
  4.   const fileMd5 = await calcFileMD5(file); // 計算文件的MD5 
  5.   const fileStatus = await checkFileExist(  // 判斷文件是否已存在 
  6.     "/exists",  
  7.     file.name, fileMd5 
  8.   ); 
  9.   if (fileStatus.data && fileStatus.data.isExists) { 
  10.     alert("文件已上傳[秒傳]"); 
  11.     return; 
  12.   } else { 
  13.     await upload({ 
  14.       url: "/single", 
  15.       file, // 文件對象 
  16.       fileMd5, // 文件MD5值 
  17.       fileSize: file.size, // 文件大小 
  18.       chunkSize: 1 * 1024 * 1024, // 分塊大小 
  19.       chunkIds: fileStatus.data.chunkIds, // 已上傳的分塊列表 
  20.       poolLimit: 3, // 限制的并發(fā)數(shù) 
  21.      }); 
  22.   } 
  23.   await concatFiles("/concatFiles", file.name, fileMd5); 

2.2 大文件并發(fā)上傳示例

定義完 uploadFile 函數(shù),要實現(xiàn)大文件并發(fā)上傳的功能就很簡單了,具體代碼如下所示:

 
 
 
 
  1.  
  2.  
  3.    
  4.      
  5.      
  6.      
  7.     大文件并發(fā)上傳示例(阿寶哥) 
  8.      
  9.      
  10.    
  11.    
  12.      
  13.     上傳文件 
  14.      
  15.    
  16.  

由于完整的示例代碼內(nèi)容比較多,阿寶哥就不放具體的代碼了。感興趣的小伙伴,可以訪問以下地址瀏覽客戶端和服務(wù)器端代碼。

  • 完整的示例代碼(代碼僅供參考,可根據(jù)實際情況進(jìn)行調(diào)整):
  • https://gist.github.com/semlinker/b211c0b148ac9be0ac286b387757e692

最后我們來看一下大文件并發(fā)上傳示例的運(yùn)行結(jié)果:

三、總結(jié)

本文介紹了在 JavaScript 中如何利用 async-pool 這個庫提供的 asyncPool 函數(shù),來實現(xiàn)大文件的并發(fā)上傳。此外,文中我們也使用了 spark-md5 這個庫來計算文件的數(shù)字指紋,如果你數(shù)字指紋感興趣的話,可以閱讀 數(shù)字指紋有什么用?趕緊來了解一下 這篇文章。

由于篇幅有限,阿寶哥并未介紹服務(wù)端的具體代碼。其實在做文件分塊合并時,阿寶哥是以流的形式進(jìn)行合并,感興趣的小伙伴可以自行閱讀一下相關(guān)代碼。如果有遇到不清楚的地方,歡迎隨時跟阿寶哥交流喲。

四、參考資源

  • 你不知道的 Blob
  • MDN - File
  • MDN - ArrayBuffer
  • MDN - HTTP請求范圍
  • JavaScript 中如何實現(xiàn)并發(fā)控制?

網(wǎng)頁標(biāo)題:JavaScript 中如何實現(xiàn)大文件并發(fā)上傳?
文章網(wǎng)址:http://www.dlmjj.cn/article/djssehc.html