“如何优化大文件上传?”,“用Web Worker!”
“如何优化大文件上传?”,“用Web Worker!”
前记
“师兄,你们之前开发的项目里面的文件上传老是上传的很慢,有时候还不成功,感觉体验不大好”。这是前段时间学校工作室的师弟跟我说的一句话,我还记得之前这部分内容是直接扔在了主线程然后用切片上传去做的,之前上传的文件很小,但随着时间的推移,上传的文件越来越大,导致上传的时候主线程开始堵塞,以至于体验开始下滑。这个时候,我在实习公司的群里看到了Web Worker
,灵感也油然而生。
web worker 是什么
用官方的话说: Web Worker 是一种在浏览器中运行的 JavaScript 脚本,可以在独立的线程中今执行,与主线程并行工作,提供了一种在后台执行复杂计算或处理耗时操作的方式,而不会堵塞主线程的执行。
用我自己的理解:就是“开小灶”,众所周知,Js是一种单线程
语言,在传统的 Js 环境中,所有代码都运行在一个主线程中,包括处理用户界面,js代码执行和网络请求。当执行耗时操作时,就会导致用户页面的卡顿和不响应。而 Web Worker 的出现允许让开发者将耗时操作放在独立的线程中执行进而不会堵塞主线程,它还可以与主线程进行通信,通过信息传递机制来交换数据和结果。总的来说,Web Worker
就是在浏览器“空闲”的时候开出一条独立于主线程的新线程,并通过“某种手段”实现与主线程通信,在不堵塞主线程的同时还提高了用户的体验。
使用
Web Worker 面对解决大文件上传的步骤
- 创建一个
Web Worker
:在主线程上使用new Worker()
构造函数创建一个Web Worker示例。指定Web Worker脚本的URL,该脚本在后台执行. - 在
Web Worker脚本
中处理文件上传处理:在Web Worker脚本中,使用onmessage
事件监听主线程发送的消息。当收到上传文件的数据时,Web Worker可以将文件数据分块处理,并将每个块上传到服务器. - 与主线程进行
通信
:Web Worker可以使用postMessage()
方法将处理结果发送回主线程。eg: 可以发送上传进度、成功或失败的信息. 监听
Web Worker的消息:在主线程中,可以使用onmessage
事件监听Web Worker发送的消息,根据接收到的消息,可以更新上传进度、显示成功或失败的消息等.
关键代码
- 创建
FileUploader
组件
import React, { useState } from "react";
const FileUploader = () => {
const [progress, setProgress] = useState(0);
const handleFileUpload = () => {
const worker = new Worker("worker.js");
worker.onmessage = (event) => {
const { type, payload } = event.data;
if (type === "progress") {
setProgress(payload);
} else if (type === "success") {
console.log("File upload successful");
// 处理上传成功的逻辑
} else if (type === "error") {
console.error("File upload failed");
// 处理上传失败的逻辑
}
};
// 获取要上传的文件
const file = document.getElementById("inputFile").files[0];
// 发送文件给Web Worker进行上传
worker.postMessage(file);
};
return (
<div>
<input type="file" id="inputFile" />
<button onClick={handleFileUpload}>上传文件</button>
<div>上传进度:{progress}%</div>
</div>
);
};
export default FileUploader;
- 创建
Web Worker
脚本
self.onmessage = (event) => {
const file = event.data;
const CHUNK_SIZE = 1024 * 1024; // 每个分块的大小,这里设置为1MB
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
let uploadedChunks = 0;
const uploadChunk = (chunk) => {
// 模拟上传过程,这里使用setTimeout来模拟异步上传
setTimeout(() => {
uploadedChunks++;
const progress = Math.floor((uploadedChunks / totalChunks) * 100);
self.postMessage({ type: "progress", payload: progress });
if (uploadedChunks === totalChunks) {
self.postMessage({ type: "success" });
} else {
// 继续上传下一个分块
uploadChunk();
}
}, 1000);
};
// 开始上传第一个分块
uploadChunk();
};
上面的代码示例中,FileUploader
组件包含一个文件选择框和一个上传按钮,当点击上传按钮,通过new Worker()
创建一个Wrb Worker示例,将文件数据传递给Web Worker。Web Worker在后台执行文件上传逻辑,并通过postMessage()
方法将上传进度和结果发送回到主线程。主线程通过监听
Web Worker的消息来更新上传进度,并根据上传结果执行相应的逻辑.
-
webpack 配置相关插件
使用
worker-loader
配置module.export = { // ... module:{ rules:[ { test:/\.worker\.js$/, use:{ loader: 'worker-loader' } } ] } }
上面配置项将匹配以
.worker.js
结尾的文件,并使用worker-loader来处理。在代码中,可以通过new Worker()
语法来创建Web Worker示例.使用
inline-loader
配置module.export = { // ... module:{ rules:[ { test:/\.worker\.js$/, use:[ { loader: 'worker-loader' }, { loader: 'inline-loader' } ] } ] } }
上述配置将使用
worker-loader
加载Web Worker脚本,并使用inline-loader
内联脚本。在代码中,可以直接使用import语法引入Web Worker脚本.
注意点
- Web Worker不能直接访问DOM,因此在处理文件上传时,可能需要将文件数据转换为
ArrayBuffer
或Blob
等格式进行处理. - Web Worker有两种类型:
Dedicated Worker
和Shared Worker
。Dedicated Worker只能被创建它的脚本所使用,而Share Worker可以被多个脚本共享。无论是哪种类型的Web Worker,它们都有自己的全局作用域,可以执行JavaScript代码,并且可以通过postMessage()
方法发送消息给创建它们的上下文,并通过onmessage
时间监听来接受消息. - 在实际使用Web Worker进行大文件上传之前,建议进行充分的测试和性能优化。可以模拟各种网络条件和文件大小,评估上传速度、内存使用和用户体验,并根据测试结果进行优化.
- 如果需要支持断点续传功能,可以在上传过程中记录已上传的块,以便在上传中断后能够从上次中断的地方继续上传。这可以通过将已上传的块信息保存在浏览器的本地存储或服务器端进行记录来实现。
待续
转载自:https://juejin.cn/post/7295565904405135401