在JavaScript中处理大文件上传时,直接传输完整文件很容易因为网络波动、请求超时等问题导致上传失败,而且一旦失败就需要重新上传整个文件,效率极低。分片传输可以将大文件切割成多个小片段,逐个上传这些片段,既能降低单次请求的压力,也支持中断后仅上传未完成的片段,大幅提升上传的稳定性和效率。
核心相关API介绍
实现大文件分片上传主要依赖以下两个JavaScript原生API:
- File API:用于获取用户选择的文件信息,包括文件名、大小、类型等,同时支持读取文件内容。
- Blob.prototype.slice:Blob对象的slice方法可以截取文件的一部分,返回一个新的Blob对象,这就是实现分片的核心方法。
分片上传的整体流程
完整的大文件分片上传流程可以分为以下几个步骤:
- 用户选择需要上传的文件,前端获取文件的基本信息。
- 根据预设的分片大小,将文件切割成多个小分片。
- 为每个分片生成唯一标识,方便后端识别和拼接。
- 逐个或并发上传分片,同时记录已上传的分片信息。
- 所有分片上传完成后,通知后端进行分片合并。
- 后端合并完成后返回最终的文件地址,前端完成上传流程。
具体实现代码示例
1. 文件选择与分片切割
首先实现文件选择功能,并将选中的文件按照指定大小切割成分片:
// 分片大小设置为2MB
const CHUNK_SIZE = 2 * 1024 * 1024;
let file = null;
let chunkList = [];
// 监听文件选择事件
document.getElementById('file-input').addEventListener('change', (e) => {
file = e.target.files[0];
if (!file) return;
// 切割文件为分片
splitFileToChunks(file);
});
// 切割文件为分片的方法
function splitFileToChunks(file) {
chunkList = [];
// 计算总分片数
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
for (let i = 0; i < totalChunks; i++) {
// 计算每个分片的起始和结束位置
const start = i * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, file.size);
// 截取分片
const chunk = file.slice(start, end);
chunkList.push({
chunk,
index: i,
totalChunks,
fileName: file.name,
// 生成分片唯一标识,这里使用文件名+分片索引+修改时间组合
chunkHash: `${file.name}-${i}-${file.lastModified}`
});
}
console.log(`文件已切割为${totalChunks}个分片`);
}
2. 分片上传实现
接下来实现分片的上传逻辑,使用FormData携带分片数据发送给后端:
// 存储已上传的分片索引
const uploadedChunks = new Set();
// 上传单个分片的方法
function uploadSingleChunk(chunkItem) {
const formData = new FormData();
// 携带分片数据和相关信息
formData.append('chunk', chunkItem.chunk);
formData.append('chunkIndex', chunkItem.index);
formData.append('totalChunks', chunkItem.totalChunks);
formData.append('fileName', chunkItem.fileName);
formData.append('chunkHash', chunkItem.chunkHash);
return fetch('https://ipipp.com/upload-chunk', {
method: 'POST',
body: formData
})
.then(res => res.json())
.then(data => {
if (data.success) {
uploadedChunks.add(chunkItem.index);
// 更新上传进度
updateUploadProgress();
return true;
}
return false;
})
.catch(err => {
console.error(`分片${chunkItem.index}上传失败`, err);
return false;
});
}
// 更新上传进度的方法
function updateUploadProgress() {
const progress = (uploadedChunks.size / chunkList.length) * 100;
document.getElementById('progress-bar').style.width = `${progress}%`;
document.getElementById('progress-text').innerText = `上传进度:${Math.round(progress)}%`;
}
3. 并发上传与合并通知
为了提升上传效率,可以采用并发上传的方式,所有分片上传完成后通知后端合并:
// 并发上传的并发数
const CONCURRENCY = 3;
// 并发上传所有分片
async function uploadAllChunks() {
// 将分片按并发数分组
const chunkGroups = [];
for (let i = 0; i < chunkList.length; i += CONCURRENCY) {
chunkGroups.push(chunkList.slice(i, i + CONCURRENCY));
}
// 依次上传每组分片
for (const group of chunkGroups) {
const uploadPromises = group.map(chunk => uploadSingleChunk(chunk));
await Promise.all(uploadPromises);
}
// 所有分片上传完成,通知后端合并
if (uploadedChunks.size === chunkList.length) {
mergeChunks();
}
}
// 通知后端合并分片
function mergeChunks() {
fetch('https://ipipp.com/merge-chunks', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
fileName: file.name,
totalChunks: chunkList.length
})
})
.then(res => res.json())
.then(data => {
if (data.success) {
console.log('文件上传完成,地址:', data.fileUrl);
}
});
}
断点续传的实现思路
断点续传可以在上传中断后,再次上传时仅上传未完成的片段,实现思路如下:
- 在上传分片前,先向后端查询该文件已上传的分片索引列表。
- 前端过滤掉已上传的分片,仅上传未完成的分片。
- 上传过程中如果中断,已上传的分片信息会保存在后端,下次上传时重复上述步骤即可。
可以在uploadAllChunks方法执行前,添加查询已上传分片的逻辑:
// 查询已上传的分片
async function getUploadedChunks(fileName) {
const res = await fetch(`https://ipipp.com/get-uploaded-chunks?fileName=${encodeURIComponent(fileName)}`);
const data = await res.json();
return data.uploadedChunkIndexes || [];
}
// 修改上传所有分片的方法,加入断点续传逻辑
async function uploadAllChunks() {
// 先查询已上传的分片
const uploadedIndexes = await getUploadedChunks(file.name);
uploadedIndexes.forEach(index => uploadedChunks.add(index));
// 过滤掉已上传的分片
const needUploadChunks = chunkList.filter(item => !uploadedChunks.has(item.index));
// 后续上传逻辑和之前一致
const chunkGroups = [];
for (let i = 0; i < needUploadChunks.length; i += CONCURRENCY) {
chunkGroups.push(needUploadChunks.slice(i, i + CONCURRENCY));
}
for (const group of chunkGroups) {
const uploadPromises = group.map(chunk => uploadSingleChunk(chunk));
await Promise.all(uploadPromises);
}
if (uploadedChunks.size === chunkList.length) {
mergeChunks();
}
}
注意事项
- 分片大小需要合理设置,太小会导致请求数量过多,太大会失去分片的意义,一般设置为1-5MB比较合适。
- 并发上传时需要控制并发数,避免同时发起过多请求导致浏览器限制或服务器压力过大。
- 分片的唯一标识需要保证唯一性,避免不同文件的分片出现冲突,可以使用文件名、文件大小、修改时间、分片索引等组合生成。
- 后端需要对应实现分片接收、存储、查询已上传分片、合并分片的逻辑,前后端接口需要提前约定好参数格式。
JavaScript大文件上传分片传输File_APIBlob修改时间:2026-06-15 18:48:52