<option id="mwy0y"><strong id="mwy0y"></strong></option>
  • <ul id="mwy0y"><sup id="mwy0y"></sup></ul>
  • <ul id="mwy0y"></ul>
  • <del id="mwy0y"><dfn id="mwy0y"></dfn></del><ul id="mwy0y"><sup id="mwy0y"></sup></ul>
  • <abbr id="mwy0y"></abbr>

    千鋒教育-做有情懷、有良心、有品質(zhì)的職業(yè)教育機構

    400-811-9990
    手機站
    千鋒教育

    千鋒學習站 | 隨時隨地免費學

    千鋒教育

    掃一掃進入千鋒手機站

    領取全套視頻
    千鋒教育

    關注千鋒學習站小程序
    隨時隨地免費學習課程

    上海
    • 北京
    • 鄭州
    • 武漢
    • 成都
    • 西安
    • 沈陽
    • 廣州
    • 南京
    • 深圳
    • 大連
    • 青島
    • 杭州
    • 重慶
    當前位置:長沙千鋒IT培訓  >  技術干貨  >  前端大文件上傳解決方案-分片上傳

    前端大文件上傳解決方案-分片上傳

    來源:千鋒教育
    發(fā)布人:qyf
    時間: 2023-02-01 18:20:00

      文件上傳功能

      我們先看一個antd的文件上傳的案例

    import React from 'react';

    import 'antd/dist/antd.css';

    import './index.css';

    import { UploadOutlined } from '@ant-design/icons';

    import { Button, message, Upload } from 'antd';

    const props = {

     name: 'file',

     action: 'https://www.mocky.io/v2/5cc8019d300000980a055e76',

     headers: {

       authorization: 'authorization-text',

    },

     

     onChange(info) {

       if (info.file.status !== 'uploading') {

         console.log(info.file, info.fileList);

      }

       if (info.file.status === 'done') {

         message.success(`${info.file.name} file uploaded successfully`);

      } else if (info.file.status === 'error') {

         message.error(`${info.file.name} file upload failed.`);

      }

     },

    };

    const App = () => (

     <Upload {...props}>

       <Button icon={<UploadOutlined />}>Click to Upload</Button>

     </Upload>

    );

    export default App;

    圖片 1

    圖片 2

      我們可以發(fā)現(xiàn),文件的上傳只進行了一次請求。

      前端在處理文件上傳時,通常也是一次性發(fā)送到server端,如果遇到大文件的時候,xhr請求會處理很長時間,這就大大增加了失敗的概率,通常我們會將大文件切片然后發(fā)送,也就是說將一個大文件的上傳問題轉化為多個小文件上傳的問題。

      文件類型

      先上一段簡單的單文件上傳和兩個類型定義

      /** A file-like object of immutable, raw data.Blobs represent data that isn't necessarily in a JavaScript-native format. The File interface is based on Blob, inheriting blob functionality and expanding it to support files on the user's system. */

      interface Blob {

      readonly size: number;

      readonly type: string;

      arrayBuffer(): Promise; // 字節(jié)數(shù)組

      slice(start?: number, end?: number, contentType?: string): Blob; // 切片的核心

      stream(): ReadableStream; // 返回ReadableStream對象(包含getReader())Blob 的內(nèi)容

      text(): Promise; // promise中返回 USVString 基本為 UTF-8 的blob字符串數(shù)據(jù)接近FileReader 的 readAsText()

      }

      /** Provides information about files and allows JavaScript in a web page to access their content. */

      interface File extends Blob {

      readonly lastModified: number;

      readonly name: string;

      }

      const upload = async (file: File) => {

      if (!file) {

      return;

      }

      const formData = new FormData();

      formData.append('file', file);

      formData.append('hash', 'true');

      const res = await fileUpload(formData);

      }

      通過定義我們知道, Blob是一個不可變、存儲文件原數(shù)據(jù)的一個類文件,但其并非是JS的原生數(shù)據(jù),而 File繼承于 Blob,使得 Blob信息擴展為用戶操作系統(tǒng)可支持的文件,并使得頁面里可以使用 Javascript訪問其文件信息。

      分片上傳

      切片

      下面我們來對文件進行切片操作

      /**

      * 文件切片

      * @param {File} file 切片文件

      * @param {number} pieceSize 切片大小

      * @param {string} fileKey 文件唯一標識

      */

      const getSliceFile = async (file: File, pieceSizes = 50, fileKey: string) => {

      const piece = 1024 * 1024 * pieceSizes;

      // 文件總大小

      const totalSize = file.size;

      const fileName = file.name;

      // 每次上傳的開始字節(jié)

      let start = 0;

      let index = 1;

      // 每次上傳的結尾字節(jié)

      let end = start + piece;

      const chunks = [];

      while (start < totalSize) {

      const current = Math.min(end, totalSize);

      // 根據(jù)長度截取每次需要上傳的數(shù)據(jù)

      // File對象繼承自Blob對象,因此包含slice方法

      const blob = file.slice(start, current);

      const hash = (await getHash(blob)) as string;

      // 特殊處理,對接阿里云大文件上傳

      chunks.push({

      file: blob,

      size: totalSize,

      index,

      fileSizeInByte: totalSize,

      name: fileName,

      fileName,

      hash,

      sliceSizeInByte: blob.size,

      fileKey,

      });

      start = current;

      end = start + piece;

      index += 1;

      }

      return chunks;

      };

      Promise.all

      文件被分成若干塊后,需要確保每一塊兒都上傳成功,也就是若干請求都成功,首先想到了Promise.all。

      // 獲取promise數(shù)組

      const getTasks = (

      files: FileInfo[],

      uploadId: string,

      fileKey: string,

      finish: number[],

      ): Promise<commonresponse_largefileuploadresponse_>[] => {

      const tasks: Promise<commonresponse_largefileuploadresponse_>[] = [];

      const currentTaskIndex: number[] = [];

      files.forEach((chunk: FileInfo) => {

      if (finish.includes(chunk.index)) {

      return;

      }

      currentTaskIndex.push(chunk.index);

      const formData = new FormData();

      formData.append('file', chunk.file);

      // @ts-ignore

      formData.append('sliceIndex', chunk.index);

      formData.append('hash', chunk.hash);

      formData.append('uploadId', uploadId);

      // @ts-ignore

      formData.append('fileSizeInByte', chunk.sliceSizeInByte);

      tasks.push(sliceUpload(formData));

      });

      return tasks;

      };

      瀏覽器連接數(shù)瓶頸

      但是這樣操作我們就會看到如下圖這種結果,先說說下圖的起因一次將所有的分片發(fā)出去,由于瀏覽器對同一個域名連接數(shù)量有限制(如:chrome是6個連接,各個瀏覽器版本和HTTP協(xié)議版本的連接數(shù)有些許差距,但是大部分都在6個左右,原則上是不超過10個連接數(shù)),這導致大量請求處于pending狀態(tài)(也就是排隊,hold在了瀏覽器,沒有發(fā)出去),后面的請求可能因為排隊而超時(超時的請求瀏覽會自動cancel了),只能將請求的超時時間設置的長一些(但是這個時間不好確定);而且還會阻塞了同域下的別的請求,這可能導致頁面不能響應UI交互了。

      基于上述問題,不能一次將請求全部發(fā)出去,那么需要確定什么時候發(fā)請求并且需要知道文件什么時候能全部上傳完畢。可以通過執(zhí)行棧或者隊列的方式,一次往?;蛘哧犃屑尤氩怀^3個的執(zhí)行單元,再通過狀態(tài)請求接口輪詢或者Promise.allSettled()獲取對應promise異步請求的結果來更新棧或者隊列,具體操作這里不再贅述。

      性能優(yōu)化 Web Workers

      大家都知道js是單線程,但是如果有多線程的方案那么對性能的影響將是巨大的,而不阻塞主線程之后體驗上也會有極大改善。一個 workers 是使用一個構造函數(shù)創(chuàng)建的一個對象(e.g. Worker()) 運行一個命名的 JavaScript 文件 - 這個文件包含將在工作線程中運行的代碼; workers 運行在另一個全局上下文中,不同于當前的window. 因此,在 Worker 內(nèi)通過 window 獲取全局作用域 和 DOM 將返回錯誤。所以你可以理解為 worker 就是新建了一個私有的有別于 window 主線程的工作線程。因為 workers 實體是js腳本,想使用的同學可以去了解下worker-plugin,可以防止babel轉換附帶進額外信息導致 workers 腳本失效。

      // 創(chuàng)建Workers

      const myWorker = new Worker('worker.js');

      // 界面發(fā)送消息給myWorker

      input1.onchange = function() {

      myWorker.postMessage([input1.value,input2.value]);

      console.log('Message posted to worker');

      }

      input2.onchange = function() {

      myWorker.postMessage([input1.value,input2.value]);

      console.log('Message posted to worker');

      }

      // 相應workers的返回

      myWorker.onmessage = function(e) {

      result.textContent = e.data;

      console.log('Message received from worker');

      }

      // worker.js 獲取兩個參數(shù)并且求和返回

      onmessage = function(e) {

      console.log('Message received from main script');

      var workerResult = 'Result: ' + (e.data[0] * e.data[1]);

      console.log('Posting message back to main script');

      postMessage(workerResult);

      }

      通過上面例子其實我們可以看出workers的本質(zhì)是運行腳本js,既然是資源那么是在client端的,所以這里就要注意同源跨域的問題,同時意味著web Workers是可以被多個window等共享使用的,也存在 SharedWorker 但是共享workers的端口配置等就會復雜不少。官方現(xiàn)在好像還沒有<script>,腳本的形式引入web workers的js, 但是可以通過 <script type="text/js-worker"> 嵌入而這種方式其實就是比較常見的數(shù)據(jù)塊的形式,而對于數(shù)據(jù)塊可以像下面一樣將函數(shù)數(shù)據(jù)化,而返回的帶hash的url, 前端的不通過后端下載指定數(shù)據(jù)都可以通過下面類似的方式獲取下載的url。

    function fn2workerURL(fn) {

     var blob = new Blob(['('+fn.toString()+')()'], {type: 'application/javascript'})

     return URL.createObjectURL(blob)

    }

    想嘗試的同學可以嘗試本地谷歌打開這個HTML會收到 Received: Hello World!

    <!DOCTYPE html>

    <html>

    <head>

    <meta charset="UTF-8" />

    <title>MDN Example - Embedded worker</title>

    <script type="text/js-worker">

    // 該腳本不會被 JS 引擎解析,因為它的 mime-type text/js-worker。

    var myVar = "Hello World!";

    // 剩下的 worker 代碼寫到這里。

    </script>

    <script type="text/javascript">

     // 該腳本會被 JS 引擎解析,因為它的 mime-type text/javascript

     function pageLog (sMsg) {

       // 使用 fragment:這樣瀏覽器只會進行一次渲染/重排。

       var oFragm = document.createDocumentFragment();

       oFragm.appendChild(document.createTextNode(sMsg));

       oFragm.appendChild(document.createElement("br"));

       document.querySelector("#logDisplay").appendChild(oFragm);

    }

    </script>

    <script type="text/js-worker">

    // 該腳本不會被 JS 引擎解析,因為它的 mime-type text/js-worker。

    onmessage = function (oEvent) {

      postMessage(myVar);

    };

    // 剩下的 worker 代碼寫到這里。

    </script>

    <script type="text/javascript">

     // 該腳本會被 JS 引擎解析,因為它的 mime-type text/javascript

     // 在過去...

     // 我們使用 blob builder

     // ...但是現(xiàn)在我們使用 Blob...:

     var blob = new Blob(Array.prototype.map.call(document.querySelectorAll("script[type=\"text\/js-worker\"]"), function (oScript) { return oScript.textContent; }),{type: "text/javascript"});

     // 創(chuàng)建一個新的 document.worker 屬性,包含所有 "text/js-worker" 腳本。

     document.worker = new Worker(window.URL.createObjectURL(blob));

     document.worker.onmessage = function (oEvent) {

       pageLog("Received: " + oEvent.data);

    };

     // 啟動 worker.

     window.onload = function() { document.worker.postMessage(""); };

    </script>

    </head>

    <body><div id="logDisplay"></div></body>

    </html>

      小伙伴們你們知道怎么辦了嗎?趕緊Get下來,自己試一試吧!

    聲明:本站稿件版權均屬千鋒教育所有,未經(jīng)許可不得擅自轉載。

    猜你喜歡LIKE

    bootstrap是什么?有什么用處

    2023-04-20

    自旋鎖原理是什么?自旋鎖有什么優(yōu)缺點

    2023-03-17

    Kafka的leader選舉機制是什么

    2023-03-03

    最新文章NEW

    多種java日志框架你真的了解嗎

    2023-03-17

    如何查看git用戶名和密碼

    2023-03-03

    2分鐘了解BFC

    2023-02-06

    相關推薦HOT

    更多>>

    快速通道 更多>>

    最新開班信息 更多>>

    網(wǎng)友熱搜 更多>>