javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > JavaScript大文件上传

JavaScript使用多线程实现一个大文件上传

作者:Lemonjing

这篇文章主要为大家详细介绍了JavaScript使用多线程实现一个大文件上传的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下

开发者: JavaScript,你给我把这十个G的文件处理一下,给文件分分片,每个分片给我计算一个hash值,服务端拿到hash值就可以知道这个分片已经上传过了(断点续传),还有整个文件也计算一下,说不定整个文件都上传过了(重复文件不用多次上传)。

JavaScript: 这活你给我干是吧,我直接给你浏览器卡死。

const CHUNK_SIZE = 5 * 1024 * 1024; // 分片大小
async function getFile(file) {
  const result = [];
  const chunkLength = Math.ceil(file.size / CHUNK_SIZE);
  for (let i = 0; i < chunkLength; i++) {
    const chunk = await getChunk(file, CHUNK_SIZE, i);
    result.push(chunk);
  }
}

function getChunk(file, size, index) {
  return new Promise((resolve, reject) => {
    const start = index * size;
    const end = start + size;
    const chunkFile = file.slice(start, end);
    const fr = new FileReader();
    fr.onload = function(e) {
      const arrBuffer = e.target.result;
      const hash = SparkMD5.ArrayBuffer.hash(arrBuffer);
      resolve({
        start,
        end,
        chunkFile,
        index,
        hash
      })
    }
    fr.readAsArrayBuffer(chunkFile);
  })
}

JavaScript:开心不,不动了吧,让你不听我的,还嘚瑟不?开发者: 不是吧,阿sir,你来真的啊?

为什么JavaScript要撂挑子呢?

在浏览器的事件循环中,我们知道不同的线程会处理不同的任务,默认的线程比如 http 线程、io 线程等等。

如果我们想在浏览器中进行复杂的计算,如果都在主线程操作,那么主线程就会阻塞,导致页面的响应不及时,造成卡顿。

有没有什么办法可以让主线程和计算线程分离呢?

答案当然是webworker啦

webworker 允许我们开启一个单独的线程,去处理一些复杂的计算任务,当计算完成之后,通过回调的形式通知主线程,主线程只要处理拿到计算结果之后的逻辑就可以了。

那我们就来学学怎么使用

1. 创建一个 worker 实例

const worker = new Worker("./worker.js");
// 如果需要指定worker的js可以使用ESM,可以添加type参数
const worker = new Worker("./worker.js", { type: "module" });

2. 告诉 worker 开始工作

这个worker是刚刚创建时候的wokerjs里面的代码哦

worker.postMessage("开始工作");

3. 监听 worker 的消息

worker.onmessage = (e) => {
  // 内部worker执行完了,或者执行到某个节点了
};

4. 关闭 worker

worker.terminate();

5. worker 内部如何与主线程通信

self.onmessage = (e) => {
  // 收到了外部worker的消息
  // 复杂逻辑
  self.postMessage("计算完成");
};

让worker代替主线程执行复杂计算

const CHUNK_SIZE = 5 * 1024 * 1024; // 分片大小
const worker = new Worker('./worker.js', {
  type: 'module'
});

fileDom.onchange = function(e) {
  const file = e.target.files[0];
  worker.postMessage([file, CHUNK_SIZE]);
}
// worker.js
self.onmessage = async (e) => {
  const [file, CHUNK_SIZE] = e.data;
  const result = [];
  const chunkLength = Math.ceil(file.size / CHUNK_SIZE);
  for (let i = 0; i < chunkLength; i++) {
    const chunk = await getChunk(file, CHUNK_SIZE, i);
    result.push(chunk);
  }
  // 处理完成了
  self.postMessage(result);
}

function getChunk(file, size, index) {
  return new Promise((resolve, reject) => {
    const start = index * size;
    const end = start + size;
    const chunkFile = file.slice(start, end);
    const fr = new FileReader();
    fr.onload = function(e) {
      const arrBuffer = e.target.result;
      const hash = SparkMD5.ArrayBuffer.hash(arrBuffer);
      resolve({
        start,
        end,
        chunkFile,
        index,
        hash
      })
    }
    fr.readAsArrayBuffer(chunkFile);
  })
}

线程嘛,开了一个就有俩,仨。。。

// 直接开启四个worker
const MAX_WORKER_NUM = 4;
const workers = new Array(MAX_WORKER_NUM).fill(0).map(() => new Worker('./worker.js', { type: 'module' }));
const wholeFileWorker = new Worker('./hashWholeFile.js', { type: 'module' });
let finishedCount = 0;
fileDom.onchange = function(e) {
  const file = e.target.files[0];
  // 计算一下一共有多少个分片
  const chunkLength = Math.ceil(file.size / CHUNK_SIZE);
  // 每一个worker要完成多少分片
  // 假如有99个分片,那第一个worker要处理1-25,第二个26-50,第三个51-75,第四个76-99
  // 我们是程序员,所以每一个index都要-1
  const workerSize = Math.ceil(chunkLength / MAX_WORKER_NUM);
  for(let i = 0; i < MAX_WORKER_NUM; i++) {
    const worker = workers[i];
    // 帮worker计算好分片任务的起始位置和结束位置
    const startIndex = i * workerSize;
    const endIndex = Math.min(start + workerSize, chunkLength);
    worker.postMessage([file, CHUNK_SIZE, startIndex, endIndex]);
    worker.onmessage = (e) => {
      finishedCount++;
      worker.terminate();
      // 计算完一部分的hash就可以开始上传了,每一个返回结果里面有index,可以告诉后端传递的是哪个分片,信息已经足够了
      
    }
  }
  // 前面先计算分片的hash,有分片计算好的hash就可以直接开始上传了
  wholeFileWorker.postMessage([file]);
  wholeFileWorker.onmessage = (e) => {
    // 最后处理整个文件的hash
    // 这样整体效果就是,用户选择文件之后,可以快速的开始上传进度条,
    // 如果是之前上传了部分,并且开始上传的分片之前已经上传好了,那么可以快速跳过这些分片,直接上传剩下的分片
    // 如果之前整个文件都上传了,那么进度条会从很少的地方直接跳到100%
  }

}
// worker.js
import "./md5.min.js"
self.onmessage = async (e) => {
  const [file, CHUNK_SIZE, startIndex, endIndex] = e.data;
  const result = [];
  for (let i = startIndex; i < endIndex; i++) {
    const chunk = await getChunk(file, CHUNK_SIZE, i);
    result.push(chunk);
  }
  // 处理完成了
  self.postMessage(result);
}

function getChunk(file, size, index) {
  return new Promise((resolve, reject) => {
    const start = index * size;
    const end = start + size;
    const chunkFile = file.slice(start, end);
    const fr = new FileReader();
    fr.onload = function(e) {
      const arrBuffer = e.target.result;
      const hash = SparkMD5.ArrayBuffer.hash(arrBuffer);
      resolve({
        start,
        end,
        chunkFile,
        index,
        hash
      })
    }
    fr.readAsArrayBuffer(chunkFile);
  })
}

// hashWholeFile.js
import "./md5.min.js"
self.onmessage = (e) => {
  const [file] = e.data;
  const hash = SparkMD5.ArrayBuffer.hash(file);
  self.postMessage(hash);
}

到此这篇关于JavaScript使用多线程实现一个大文件上传的文章就介绍到这了,更多相关JavaScript大文件上传内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文