JavaScript使用多線程實(shí)現(xiàn)一個(gè)大文件上傳
開(kāi)發(fā)者: JavaScript,你給我把這十個(gè)G的文件處理一下,給文件分分片,每個(gè)分片給我計(jì)算一個(gè)hash值,服務(wù)端拿到hash值就可以知道這個(gè)分片已經(jīng)上傳過(guò)了(斷點(diǎn)續(xù)傳),還有整個(gè)文件也計(jì)算一下,說(shuō)不定整個(gè)文件都上傳過(guò)了(重復(fù)文件不用多次上傳)。
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:開(kāi)心不,不動(dòng)了吧,讓你不聽(tīng)我的,還嘚瑟不?開(kāi)發(fā)者: 不是吧,阿sir,你來(lái)真的???
為什么JavaScript要撂挑子呢?
在瀏覽器的事件循環(huán)中,我們知道不同的線程會(huì)處理不同的任務(wù),默認(rèn)的線程比如 http 線程、io 線程等等。
如果我們想在瀏覽器中進(jìn)行復(fù)雜的計(jì)算,如果都在主線程操作,那么主線程就會(huì)阻塞,導(dǎo)致頁(yè)面的響應(yīng)不及時(shí),造成卡頓。
有沒(méi)有什么辦法可以讓主線程和計(jì)算線程分離呢?
答案當(dāng)然是webworker啦
webworker 允許我們開(kāi)啟一個(gè)單獨(dú)的線程,去處理一些復(fù)雜的計(jì)算任務(wù),當(dāng)計(jì)算完成之后,通過(guò)回調(diào)的形式通知主線程,主線程只要處理拿到計(jì)算結(jié)果之后的邏輯就可以了。
那我們就來(lái)學(xué)學(xué)怎么使用
1. 創(chuàng)建一個(gè) worker 實(shí)例
const worker = new Worker("./worker.js");
// 如果需要指定worker的js可以使用ESM,可以添加type參數(shù)
const worker = new Worker("./worker.js", { type: "module" });
2. 告訴 worker 開(kāi)始工作
這個(gè)worker是剛剛創(chuàng)建時(shí)候的wokerjs里面的代碼哦
worker.postMessage("開(kāi)始工作");
3. 監(jiān)聽(tīng) worker 的消息
worker.onmessage = (e) => {
// 內(nèi)部worker執(zhí)行完了,或者執(zhí)行到某個(gè)節(jié)點(diǎn)了
};
4. 關(guān)閉 worker
worker.terminate();
5. worker 內(nèi)部如何與主線程通信
self.onmessage = (e) => {
// 收到了外部worker的消息
// 復(fù)雜邏輯
self.postMessage("計(jì)算完成");
};
讓worker代替主線程執(zhí)行復(fù)雜計(jì)算
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);
})
}
線程嘛,開(kāi)了一個(gè)就有倆,仨。。。
// 直接開(kāi)啟四個(gè)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];
// 計(jì)算一下一共有多少個(gè)分片
const chunkLength = Math.ceil(file.size / CHUNK_SIZE);
// 每一個(gè)worker要完成多少分片
// 假如有99個(gè)分片,那第一個(gè)worker要處理1-25,第二個(gè)26-50,第三個(gè)51-75,第四個(gè)76-99
// 我們是程序員,所以每一個(gè)index都要-1
const workerSize = Math.ceil(chunkLength / MAX_WORKER_NUM);
for(let i = 0; i < MAX_WORKER_NUM; i++) {
const worker = workers[i];
// 幫worker計(jì)算好分片任務(wù)的起始位置和結(jié)束位置
const startIndex = i * workerSize;
const endIndex = Math.min(start + workerSize, chunkLength);
worker.postMessage([file, CHUNK_SIZE, startIndex, endIndex]);
worker.onmessage = (e) => {
finishedCount++;
worker.terminate();
// 計(jì)算完一部分的hash就可以開(kāi)始上傳了,每一個(gè)返回結(jié)果里面有index,可以告訴后端傳遞的是哪個(gè)分片,信息已經(jīng)足夠了
}
}
// 前面先計(jì)算分片的hash,有分片計(jì)算好的hash就可以直接開(kāi)始上傳了
wholeFileWorker.postMessage([file]);
wholeFileWorker.onmessage = (e) => {
// 最后處理整個(gè)文件的hash
// 這樣整體效果就是,用戶選擇文件之后,可以快速的開(kāi)始上傳進(jìn)度條,
// 如果是之前上傳了部分,并且開(kāi)始上傳的分片之前已經(jīng)上傳好了,那么可以快速跳過(guò)這些分片,直接上傳剩下的分片
// 如果之前整個(gè)文件都上傳了,那么進(jìn)度條會(huì)從很少的地方直接跳到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);
}
到此這篇關(guān)于JavaScript使用多線程實(shí)現(xiàn)一個(gè)大文件上傳的文章就介紹到這了,更多相關(guān)JavaScript大文件上傳內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
學(xué)習(xí)JavaScript編程語(yǔ)言的8張思維導(dǎo)圖分享
這篇文章主要介紹了學(xué)習(xí)JavaScript編程語(yǔ)言的8張思維導(dǎo)圖分享,本文給出了javascript變量、javascript運(yùn)算符、javascript數(shù)組、javascript流程語(yǔ)句、javascript字符串函數(shù)、javascript函數(shù)基礎(chǔ)、javascript基礎(chǔ)DOM操作、javascript正則表達(dá)式的思維導(dǎo)圖,需要的可以參考下2015-03-03
微信公眾平臺(tái)API錯(cuò)誤代碼41002的問(wèn)題解決辦法
今天在研究微信公眾號(hào)接口問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于微信公眾平臺(tái)API錯(cuò)誤代碼41002問(wèn)題的解決辦法,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-09-09
微信小程序基于本地緩存實(shí)現(xiàn)點(diǎn)贊功能的方法
這篇文章主要介紹了微信小程序基于本地緩存實(shí)現(xiàn)點(diǎn)贊功能的方法,涉及微信小程序界面布局、事件響應(yīng)及緩存操作等相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-12-12
前端如何寫(xiě)一個(gè)大文件上傳組件詳細(xì)步驟
在現(xiàn)代Web應(yīng)用中,大文件上傳是一個(gè)常見(jiàn)的需求,尤其是在云存儲(chǔ)、社交媒體和協(xié)作平臺(tái)等領(lǐng)域,這篇文章主要介紹了前端如何寫(xiě)一個(gè)大文件上傳組件詳細(xì)步驟,需要的朋友可以參考下2025-04-04
JavaScript去除文件名稱(chēng)中的后綴名的多種方法
本文介紹了JavaScript中去除文件名后綴名的四種方法,并推薦使用substring()和lastIndexOf()方法,因?yàn)樗?jiǎn)潔易懂,性能較好,并能處理特殊情況,需要的朋友可以參考下2025-12-12
原生js實(shí)現(xiàn)對(duì)Ajax的封裝(仿jquery)
這篇文章主要為大家詳細(xì)介紹了原生js實(shí)現(xiàn)對(duì)Ajax的封裝,模仿jquery,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01
JSONP獲取Twitter和Facebook文章數(shù)的具體步驟
這篇文章主要介紹了JSONP獲取Twitter和Facebook文章數(shù)的方法,需要的朋友可以參考下2014-02-02

