一般来说,我们的js文件下载会使用window.location.href定位到后端的接口,后端生成文件返回,然后浏览器自动下载。这种方法最简单,但是无法获取下载成功的通知,在大文件生成和下载的时候,时间过长,用户可能会重复进行下载的点击,对服务器造成负担。
因此要用另外的方式发送请求来监听文件下载完成,此方法使用的是XMLHttpRequest来请求,可以监听文件下载完成,另外一提,如果要对下载使用进度条监听的话,可以使用设置cookie循环向后端请求获取进度的方法,本文只讨论前者。
不多bb,直接开始
这是我一开始的版本:
load = function() {
//填写你的下载时加载的提示
}
disload = function() {
//下载完成后触发,用来关闭提示框
}
getDownload = function(url) {
load();
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true); // 也可用POST方式
xhr.responseType = "blob";
xhr.onload = function () {
if (this.status === 200) {
var blob = this.response;
var reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = function (e) {
var headerName = xhr.getResponseHeader("Content-disposition");
var fileName = decodeURIComponent(headerName).substring(20);
var a = document.createElement('a');
a.download = fileName + ".xls";
a.href = e.target.result;
$("body").append(a); // 修复firefox中无法触发click
a.click();
$(a).remove();
};
disload();
}
};
xhr.send()
};
这个原理是通过XMLHttpRequest发送请求,利用FileReader来读取文件内容,关键是设置a标签,将blob的文件内容转换为base64并放入a标签的href中,模拟点击来进行下载。
但是需要注意的是
- a标签的download经测试,仅在谷歌和火狐浏览器生效,在edge和IE下不触发下载
- 如果以base64为a标签的url地址的话,由于长度限制最多只能下载大约2M的文件,再高的话就会下载失败
第一个问题
对于ie等浏览器来说,有另外的一种方法来触发下载。
function dataURLtoBlob(dataurl) {
var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
}
//e.target.result 正是上面代码中转换后的baseUrl,这种方法要转换为blob
var blob = dataURLtoBlob(e.target.result);
navigator.msSaveBlob(blob, fileName + ".xls");
需要注意的是navigator.msSaveBlob在谷歌等浏览器中并不存在,在此,就解决了第一个问题
第二个问题
第二个问题其实也看需求,对于非常小的文件,比如小图标等可以用base64作为url地址,对于其他的大文件,可以将文件存入内存中,用虚拟内存地址指向。
var blob = dataURLtoBlob(e.target.result);
var a = document.createElement('a');
a.download = fileName + ".xls";
a.href = URL.createObjectURL(blob);
$("body").append(a); // 修复firefox中无法触发click
a.click();
URL.revokeObjectURL(a.href);
$(a).remove();
URL.createObjectURL 将blob形式的文件存入并返回一个url以供下载。
而URL.revokeObjectURL则将该内存释放。
至此,两个问题解决,可以看到大文件根本不能使用base64,直接使用blob是最好的。因此合并起来就可以得到最终版本了:
最终版本
load = function() {
//填写你的下载时加载的提示
}
disload = function() {
//下载完成后触发,用来关闭提示框
}
getDownload = function(url) {
load();
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true); // 也可用POST方式
xhr.responseType = "blob";
xhr.onload = function () {
if (this.status === 200) {
var blob = this.response;
if (navigator.msSaveBlob == null) {
var a = document.createElement('a');
var headerName = xhr.getResponseHeader("Content-disposition");
var fileName = decodeURIComponent(headerName).substring(20);
a.download = fileName;
a.href = URL.createObjectURL(blob);
$("body").append(a); // 修复firefox中无法触发click
a.click();
URL.revokeObjectURL(a.href);
$(a).remove();
} else {
navigator.msSaveBlob(blob, fileName);
}
}
disload();
};
xhr.send()
};