JS大文件分片上传实现详解:AbortController与分片策略

作者:半夏时间:2026-10-30 19:45:12浏览:548

本文详细解析JavaScript中大文件分片上传的完整实现流程。通过AbortController接口实现请求中断,利用File.slice进行分片切割,并结合FileReader异步读取与spark-md5生成文件签名。文章涵盖客户端分片逻辑、重试机制及服务端合并策略,提供从配置到执行的代码示例,帮助开发者掌握高可靠性的大文件上传方案。

掌握AbortController

AbortController接口代表一个控制对象,允许在需要时中止一个或多个Web请求。

利用该接口可实现取消或中断请求的功能。

axios.abort()的底层实现即为此机制。

了解

fse = require('fs-extra')

概述

文件分片上传的原理是将文件转换为ArrayBlob形式,利用file(blob)的slice方法将文件拆分为多个部分,按顺序传输至服务端,传输完成后调用服务端的merge合并接口,由服务端完成文件合并。

服务端实现原理为:上传前创建文件夹(命名可参考提供的代码),随后通过fse的写入方法将获取的文件写入创建的文件夹中,并保持相应排序。最后通过合并方法将文件合并为一个完整文件。

深入学习

客户端

getChunkListAndFileMd5函数

该函数用于生成分片数组及生成Hash签名。

生成分片列表数组,采用file.prototype.slice方法。为解决兼容性问题,使用的代码如下:

export function getBlobSlice() {
    return (File.prototype.slice ||
        File.prototype.mozSlice ||
        File.prototype.webkitSlice);
}

生成分片:getBlobSlice.call(file,start,end)。其中start、end分别为起始位置和终点位置,确定分片大小即可计算start和end。

接下来开始,定义size为5M,代码如下

const DEFAULT_CHUNK_SIZE = 5 * 1024 * 1024;

定义一个对象保存默认配置:

const DEFAULT_OPTIONS = {
    chunkSize: DEFAULT_CHUNK_SIZE,
};

定义分片编号,初始值为0。定义chunkSize保存上述定义的分片大小常量。

let currentChunk = 0;
const chunkSize = this.fileUploaderClientOptions.chunkSize; 

接下来计算所需分片数量,公式为:文件总大小/每个分片大小。结果可能为小数,为确保分片完整,需向上取整。

const chunks = Math.ceil(file.size / chunkSize);

定义一个函数来加载分片,代码如下:

function loadNextChunk() {
    const start = currentChunk * chunkSize;
    const end = start + chunkSize >= file.size ? file.size : start + chunkSize;
    const chunk = blobSlice.call(file, start, end);
    chunkList.push(chunk);
    fileReader.readAsArrayBuffer(chunk);
}

解析该函数:

1.计算start位置,这不难理解。

2.计算end位置,此处需判断两种情况:

文件尺寸较小,小于分片大小,直接取文件尺寸。

最后一个切片,大小可能小于分片尺寸,直接取文件大小的位置。

3.调用上述函数实现分片,并获取当前分片。

4.将当前分片push到数组中。

5.调用fileReader.readAsArrayBuffer方法读取分片。(当前分片为blob实例,通过此方法可读取其内容)。 接下来看看fileReader的相关机制。

认识FileReader

若不懂fileReader,可先查阅文档,了解其方法。

FileReader对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用File或Blob对象指定要读取的文件或数据。 其中File对象可以是

  • 来自用户在元素上选择文件后返回的FileList对象
  • 也可以来自拖放操作生成的DataTransfer对象,
  • 还可以来自在HTMLCanvasElement上执行mozGetAsFile()方法后返回的结果。

FileReader可在Web Worker中使用。(重要,可极大程度解决性能问题)

此处因用到FileReader的方法,故重点讲解该对象的方法。

  • FileReader.abort();中止读取操作。
  • FileReader.readAsArrayBuffer();开始读取指定Blob中的内容,完成后,result属性中保存的是被读取文件的ArrayBuffer数据对象。
  • FileReader.readAsDataURL();开始读取指定Blob中的内容。完成后,result属性中将包含一个data:URL格式的Base64字符串以表示所读取文件的内容。
  • FileReader.readAsText();开始读取指定Blob中的内容。完成后,result属性中将包含一个字符串以表示所读取的文件内容。

目前需读取分片内容,且需要ArrayBuffer格式,因此创建FileReader对象实例,并使用readAsArrayBuffer方法读取文件,通过onload事件监听,获取最终结果。代码如下:

const fileReader = new FileReader();
fileReader.onload = function (e) {
    // 我们读取到的结果是 e.target.result
}
fileReader.onerror = function (e) {
    // 这里表示读取失败
}

上述loadNextChunk函数中调用了此方法。

fileReader.readAsArrayBuffer(chunk);

使用md5实现签名

用到了spark-md5这个第三方库。主要使用的方法是生成md5编码。

npm install --save spark-md5

npmjs中的spark-md5

github

这是一个处理分片的库。提供了方法:

const spark = new SparkMD5.ArrayBuffer();

将分片v添加到对象中

spark.append(v)

最终结果返回

const result = spark.end()

uploadFile函数

该函数用于上传文件。

首先获取上述函数得到的值md5、chunkList。

const { md5, chunkList } = yield this.getChunkListAndFileMd5(file);

上传文件前,需调用接口初始化文件分片上传,此处即调用初始化文件分片上传的接口requestOptions.initFilePartUploadFunc。

yield requestOptions.initFilePartUploadFunc();

该接口在后端的实现即创建好一个文件夹用来保存分片,此处不多赘述,后续在学习Server代码时细讲。 接下来开始上传我们的分片,上传方法requestOptions.uploadPartFileFunc:

for (let index = 0; index < chunkList.length; index++) {
    try {
        yield requestOptions.uploadPartFileFunc(chunkList[index], index);
    }
    catch (e) {
        console.warn(`${index} part upload failed`);
        retryList.push(index);
    }
}

注意:无法保证全部顺利上传,若中间出现问题中断了上传等,我们如何处理?

此处使用retryTimes来获取需要重新上传的列表。

for (let retry = 0; retry < requestOptions.retryTimes; retry++) {
    if (retryList.length > 0) {
        console.log(`retry start, times: ${retry}`);
        for (let a = 0; a < retryList.length; a++) {
            const blobIndex = retryList[a];
            try {
                yield requestOptions.uploadPartFileFunc(chunkList[blobIndex], blobIndex);
                retryList.splice(a, 1);
            }
            catch (e) {
                console.warn(`${blobIndex} part retry upload failed, times: ${retry}`);
            }
        }
    }
}

最后调用上传结束的接口requestOptions.finishFilePartUploadFunc(md5),该接口主要通知服务端分片都上传完了,服务端可进行文件合并,最终将分片合并成一个文件。

if (retryList.length === 0) {
    return yield requestOptions.finishFilePartUploadFunc(md5);
}
else {
    throw Error(`upload failed, some chunks upload failed: ${JSON.stringify(retryList)}`);
}

至此,客户端的操作完毕!

完整代码

以上就是一文带你彻底搞懂JS大文件分片上传实现的详细内容,更多关于JS大文件分片上传的资料请关注本站其它相关文章!

作者最新文章