这是前端所有的下载方式了吧?(二)

发布于:2024-04-30 ⋅ 阅读:(21) ⋅ 点赞:(0)

本系列分为两篇,第一篇总结常用下载方式,不需要认证的文件或者需要认证的小文件下载。第二篇总结的是从需要认证的接口进行大文件下载的几种情况。(分篇依据是从建议使用的维度进行分类,理论上讲,需要认证的几种下载方式也可以进行不需要认证的文件下载)

1. 分片下载

对于大文件,很容易想到的是分片下载,分片下载的好处是可是借助浏览器能同时进行多个http请求的特性加速下载,对于网络不稳定的情况也能采取重新下载某些分片的措施,节省时间。

分片下载的前后端分工如下。

后端:1. 告知前端文件大小 2. 下载文件的指定分片

前端:1. 获取需要下载的文件大小 2. 计算分片数,每个分片的起始和结束位置 3. 下载分片 4. 合并分片并生成文件

前端代码实现如下:

// 1. 获取文件大小,单位Byte
let { data: { size, fileName } } = await this.$axios.post('http://localhost:3000/getSize')
// 2. 分片拆分、下载
// range写在header里的话会让控制台链接状态码为206,可以理解为分片下载专用
// mdn描述:206 Partial Content 成功状态响应代码表示请求已成功,并且主体包含所请求的数据区间,该数据区间是在请求的 Range 首部指定的。
const promiseTasks = []
const step = 1024 * 1024 // 单位Byte
for(let i = 0; i < Math.ceil(size/step); i++) {
    const start = i * step;
    const end = Math.min((i+1) * step - 1, size)
​
    promiseTasks.push(
        new Promise((resolve,reject) => {
            this.$axios.post(`http://localhost:3000/downloadRange/${fileName}`,{},{
                headers: {
                    responseType: 'blob',
                    Range: `bytes=${start}-${end}`
                }
            }).then(
                res => {
                    resolve(res.data)
                }
            )
        })
    )
}
const dataArr = await Promise.all(promiseTasks)
// 3. 合并分片,保存
const blob = new Blob(dataArr)
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName;
a.click();
window.URL.revokeObjectURL(url);
a.remove();

可以看出,这种方式下载的大文件仍然是先将分片存在了内存上,下载完成后在内存中合并,对于内存较小的机器扔然不是很受用。

2. 流式获取,边获取边下载

对于大文件,每个浏览器都有自己的Max Blob Size,所以可下载的文件也不能无限制的大,即使浏览器不显示,内存条也不允许。既要保证安全性,又要降低内存损耗,就像看电影一样边缓存边播放,文件流边获取边保存是一种比较理想的实现方案了。

这就要用到浏览器提供给js的流式操作接口,看代码:

// 这里采用的是StreamSaver.js库实现的
const script = document.createElement("script")
script.src = "https://cdn.jsdelivr.net/npm/streamsaver@2.0.3/StreamSaver.min.js"
document.body.appendChild(script)
​
// 创建写入器,并声明要写入的文件(文件会自动创建,这里任意命名)
const fileStream = window.streamSaver.createWriteStream('helloworld.txt')
​
fetch('http://localhost:3000/download',{
    method: 'post'
}).then(res => {
    if (window.WritableStream && readableStream.pipeTo) {
        // 一步搞定读取+写入
        return res.body.pipeTo(fileStream)
            .then(() => console.log('完成写入'))
    }
    // 部分浏览器不兼容writableStream, StreamSaver.js做了兼容处理,采用的方式就是先用blob接收,再用createObjectURL下载
    const writer = fileStream.getWriter()
    const reader = res.body.getReader()
    const write = () => {
        reader.read().then(res => {
            if(res.done) {
                writer.close()
            }else {
                writer.write(res.value).then(write)
            }
        }).catch(err => {
            console.log(err)
        })
    }
    write()
})
// 注意:目前笔者查询的资料显示,axios不支持直接处理流式数据,所以使用这种方式下载时建议使用fetch,各位大佬如果有实现方案,感谢分享。

这里实现采用的是StreamSaver.js库做的下载,至于原生代码,笔者还未完全掌握,通过StreamSaver仓库的描述看(Just want to let you know that there is this new native way to save files to the HD: https://github.com/whatwg/fs which is more or less going to make FileSaver, StreamSaver and similar packages a bit obsolete in the future),目前又有了一种新的原生方式去下载文件。至于Stream Api原生下载及这种新的下载方式如何编码,待本人研究明白后再做总结。

3. WebWorker

WebWorker本身并不具备文件下载功能,这里仅做提示,分片下载也好,流式下载也罢,当文件过大时,处理数据的过程中都会阻碍js主线程的执行,导致页面卡住,非常影响用户体验,可使用WebWorker新建一个线程来完成这个工作。至于WebWorker的使用方式,待日后总结。


网站公告

今日签到

点亮在社区的每一天
去签到