JavaScript使用多線程實現(xiàn)一個大文件上傳
開發(fā)者: JavaScript,你給我把這十個G的文件處理一下,給文件分分片,每個分片給我計算一個hash值,服務端拿到hash值就可以知道這個分片已經(jīng)上傳過了(斷點續(xù)傳),還有整個文件也計算一下,說不定整個文件都上傳過了(重復文件不用多次上傳)。
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:開心不,不動了吧,讓你不聽我的,還嘚瑟不?開發(fā)者: 不是吧,阿sir,你來真的???
為什么JavaScript要撂挑子呢?
在瀏覽器的事件循環(huán)中,我們知道不同的線程會處理不同的任務,默認的線程比如 http 線程、io 線程等等。
如果我們想在瀏覽器中進行復雜的計算,如果都在主線程操作,那么主線程就會阻塞,導致頁面的響應不及時,造成卡頓。
有沒有什么辦法可以讓主線程和計算線程分離呢?
答案當然是webworker啦
webworker 允許我們開啟一個單獨的線程,去處理一些復雜的計算任務,當計算完成之后,通過回調(diào)的形式通知主線程,主線程只要處理拿到計算結果之后的邏輯就可以了。
那我們就來學學怎么使用
1. 創(chuàng)建一個 worker 實例
const worker = new Worker("./worker.js");
// 如果需要指定worker的js可以使用ESM,可以添加type參數(shù)
const worker = new Worker("./worker.js", { type: "module" });
2. 告訴 worker 開始工作
這個worker是剛剛創(chuàng)建時候的wokerjs里面的代碼哦
worker.postMessage("開始工作");
3. 監(jiān)聽 worker 的消息
worker.onmessage = (e) => {
// 內(nèi)部worker執(zhí)行完了,或者執(zhí)行到某個節(jié)點了
};
4. 關閉 worker
worker.terminate();
5. worker 內(nèi)部如何與主線程通信
self.onmessage = (e) => {
// 收到了外部worker的消息
// 復雜邏輯
self.postMessage("計算完成");
};
讓worker代替主線程執(zhí)行復雜計算
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,可以告訴后端傳遞的是哪個分片,信息已經(jīng)足夠了
}
}
// 前面先計算分片的hash,有分片計算好的hash就可以直接開始上傳了
wholeFileWorker.postMessage([file]);
wholeFileWorker.onmessage = (e) => {
// 最后處理整個文件的hash
// 這樣整體效果就是,用戶選擇文件之后,可以快速的開始上傳進度條,
// 如果是之前上傳了部分,并且開始上傳的分片之前已經(jīng)上傳好了,那么可以快速跳過這些分片,直接上傳剩下的分片
// 如果之前整個文件都上傳了,那么進度條會從很少的地方直接跳到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使用多線程實現(xiàn)一個大文件上傳的文章就介紹到這了,更多相關JavaScript大文件上傳內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
解決input輸入框僅支持輸入數(shù)字及兩位小數(shù)點的限制
這篇文章主要為大家介紹了解決input輸入框僅支持輸入數(shù)字及兩位小數(shù)點的限制技巧示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-11-11
前端插件之Bootstrap Dual Listbox使用教程
這篇文章主要介紹了前端插件之Bootstrap Dual Listbox使用教程,本文給大家介紹的非常詳細,具有一定的參考借鑒價值 ,需要的朋友可以參考下2019-07-07

