谷歌浏览器支持数据存储最大2GB。
文件下载
在我们实现文件下载功能时,你知道其中是怎么样的流程吗?
首先,我们先发送请求,将下载所需的参数组合,通过http请求发送到后端,此时f12中可以看到请求是处于发送中。
然后后端接收到请求,开始将要下载的内容往服务器硬盘里面存,这段时间,前端发送的请求都是在等待,没有什么响应,也接收不到下载进度(因为这时候后端还在准备数据,将数据打包成zip包,还未将数据返给前端)。
当后端将数据都放到服务器硬盘后,并打包好,开始和前端接口建立文件流数据传输通道,这时候请求是可以拿到资源传输整体大小,我们也可以在这个时候实现加载进度条。
当所有数据都返回给前端时,我们可以在f12中的这个请求中的响应中看到返回的数据。这之前都是空白。
超大文件下载
下载超大文件,我们不应该使用new Blob这种将数据先存储到内存,然后转下载资源再进行下载。因为当资源很大时,这些资源都是先存储到内存的,浏览器分配到的内存是有限的,过多的占用内存会导致浏览器卡顿、崩溃。
而使用window.open(url)打开一个路径,其实就是发送请求,但是让浏览器帮我们处理数据,则不会出现这种情况。
下面举个例子:
new Blob这种先存内存的方式(适用于小文件):
const config = {
headers: {
'Content-Type': 'application/json'
},
withCredentials: true,
responseType: 'arraybuffer'
};
const params = {
id_kind: '',
id_no: '',
query_flag: ''
};
const res = await axios.post(downloadUrl, params, config);
if (res.code === '200') {
try {
// 处理接口请求成功但是下载错误返回json格式的情况
//如果JSON.parse(enc.decode(new Uint8Array(res.data)))不报错,说明后台返回的是json对象,则弹框提示
//如果JSON.parse(enc.decode(new Uint8Array(res.data)))报错,说明返回的是文件流,进入catch,下载文件
let enc = new TextDecoder('utf-8')
let data = JSON.parse(enc.decode(new Uint8Array(res.data))) //转化成json对象
if (data.message) {
// 下载错误
setTimeout(this.msgList[msgIndex], 0)
return this.$hMessage.error(data.message)
}
} catch(err) {
// 下载成功:
// 处理参数:接口返回参处理、下载格式、文件名
let blob = new Blob([res.data], {
type: res.headers["content-type"]
});
const disposition = decodeURI(res.headers['content-disposition'])
const match = disposition ? disposition.match(/attachment; filename="(.+)"/i) : null;
const filename = match ? match[1] : 'unknowfile'
// 下载
if (typeof window.navigator.msSaveBlob !== 'undefined') {
// 兼容IE,window.navigator.msSaveBlob:以本地方式保存文件
window.navigator.msSaveBlob(blob, filename);
this.$hMessage.success('下载成功')
} else {
// 普通浏览器
let url = window.URL.createObjectURL(blob);
if (url) {
let link = document.createElement('a');
link.style.display = 'none';
link.href = url;
link.setAttribute('download', filename);
document.body.appendChild(link);
link.click();
document.body.removeChild(link); //下载完成移除元素
window.URL.revokeObjectURL(url); //释放掉blob对象
this.$hMessage.success('下载成功')
}
}
}
} else {
this.$hMessage.success('下载失败')
}
window.open这种交给浏览器处理的方式:
import { Base64 } from './js-base64.js';
const params = {
id_kind: '',
id_no: '',
query_flag: ''
};
const base64Str = Base64.encodeURI(JSON.stringify(params)); // 将查询参数转为base64,如果不会很长的话就不用
const url = `${downloadUrl}?base64Str=${base64Str}`;
const openWin = window.open(url);
// 简单实现监听下载完毕
const timer = setInterval(_ => {
if (openWin.closed) {
console.log('下载成功'); // 资源加载完毕,此时可选择下载存放目录或者取消下载,选择后新打开的页签自动关闭
clearInterval(timer);
timer = null;
}
}, 1000);
window.open()虽然能解决浏览器下载超大文件的问题,但是新打开一个页签,并且页签内空白内容,需要等后端建立数据传输时才会关闭,假设这个数据传输建立得很慢(建立之前需要将数据进行打包),那么就会有长时间的等待。体验上不是很好。
所以还有一种方式:ReadableStream。
ReadableStream这种交给浏览器处理的方式:
ReadableStream的文档说明:developer.mozilla.org/zh-CN/docs/…
fetch的文档说明:developer.mozilla.org/zh-CN/docs/…
export default {
methods: {
urlLOcalizecBlob(url) {
const localBlob = null, filename = 'unknowfile';
// fetch是es6的请求语法,返回一个promise
fetch(url, {
method: 'GET',
mode: 'cors',
credentials: 'include',
headers: {
Accept: '*/*'
}
}).then(response => {
// 使用fetch时,如果要从响应头中获取数据,需要通过get方法
// 返回报错
if (response.header.get('content-type').includes('application/json')) {
// 处理如果返回的是json格式,那说明请求报错,需要将报错信息展示
// response.json()将信息json化,返回一个Promise,还有其他很多方法,可以在mdn搜fetch
return response.json().then(err => {
// throw new Error()可以将错误抛出,直接进入到catch,另外括号里放String
// err.message是后端返回的报错信息文字
throw new Error(err.message)
})
}
// 返回文件流
const contentDisposition = response.headers.get('content-disposition');
const disposition = decodeURI(contentDisposition);
const match = disposition ? disposition.match(/attachment; filename="(.+)"/i) : null;
filename = match ? match[1] : 'unknowfile';
const reader = response.body.getReader();
return new ReadableStream({
start(controller) {
return pump();
function pump() {
return reader.read().then(({done, value}) => {
if (done) {
controller.close();
return;
}
controller.enqueue(value);
return pump()
})
}
}
})
}).then(stream => new Response(stream)).then(response => response.blob()).then(blob => {
this.fileDownload(filename, blob)
}).catch(err => {
this.$hNotice.error(err?.message || '下载失败');
})
}
},
fileDownload(filename, blob) {
if (typeof window.navigator.msSaveBlob !== 'undefined') {
// ie浏览器
window.navigator.msSaveBlob(blob, filename);
this.$hMessage.success('下载成功')
} else {
// 其他浏览器
let url = window.URL.createObjectURL(blob);
if (url) {
let link = document.createElement('a');
link.style.display = 'none';
link.href = url;
link.setAttrbute('download', filename);
document.body.appendChild(link);
link.click();
// 释放内存,释放blob对象
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
link = null;
this.$hMessage.success('下载成功')
}
}
}
}
如果get请求参数太长,或者说url拼接太长,可以考虑js-base,将之转为base64。