XMLHttpRequest Level 2 实现文件下载

XMLHttpRequest level 2 中可以利用 Ajax 实现对二进制文件的下载功能,通过设置 responseType='blob'
告诉浏览器放回的数据类型为二进制流大文件类型,即可触发浏览器的下载功能。

依赖

  • element-ui 加载层、提示层模块
  • moment 时间日期格式处理模块

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// 如果想使用其他提示层,可以修改提示部分的逻辑代码
import { Loading, Message } from "element-ui";
import moment from 'moment';

/**
* @author: hongwenqing
* @desc: 文件下载功能
* @url: 请求地址
* @data: post 请求参数
* @params: get 请求参数
* @method: 请求方式
* @filename: 自定义文件名
* @loadingText: loading 提示文本内容
* @errorText: 下载失败时提示文本
* @returns: Promise
*/

const download = ({
url, // 请求地址
data, // post 请求参数
params, // get 请求参数
method = "post", // 请求方式
filename, // 文件名
loadingText = "正在下载...", // loading 提示文本内容
errorText = "下载失败", // 下载失败时提示文本
handleErr // 自定义文件下载时,系统级抛错时处理函数,非 http 错误
}) => {
let loadingInstance = Loading.service({
// 创建加载提示实例
background: "transparent",
fullscreen: true,
text: loadingText
});

// for post
data = data ? JSON.stringify(data) : false;

// for get
if (params) {
url += '?' + decodeURIComponent( new URLSearchParams( params ) )
}

return new Promise((resolve, reject) => {
let x = new XMLHttpRequest();
x.open(method, url, true);
if (method === "post" || method === "POST")
x.setRequestHeader("content-type", "application/json;charset=utf-8"); // 设置请求头,格式为json
x.responseType = "blob"; // 返回类型为二进制流
x.onload = function(e) {
let client_msg = errorText,
blob = this.response,
status = this.status;

// 获取文件名
filename = getFileName( filename , this )

if (status == 504) {
client_msg = "网络忒卡,连接超时";
}

loadingInstance.close(); // 关闭

if (status == 200) {

//文件下载/导出异常处理
if (blob.type.indexOf("application/json") !== -1) {
blobToJson(blob).then(res => {
handleErr && handleErr(res);
});
return;
}

//文件正常下载
if (window.navigator.msSaveOrOpenBlob) {
// for IE
window.navigator.msSaveOrOpenBlob(blob, filename);
} else {
// 现代浏览器
let eleLink = document.createElement("a"), // 创建隐藏的可下载链接
objectUrl = URL.createObjectURL(blob); // 将内容转为 blob 地址

eleLink.setAttribute("download", filename); // 设置文件名
eleLink.style.display = "none";
eleLink.href = objectUrl;
document.body.appendChild(eleLink); // 插入body
eleLink.click(); // 触发点击
document.body.removeChild(eleLink); // 然后移除
}
resolve();
} else {
Message.error({
message: client_msg
});
reject(e);
}
};

x.onerror = function(e) {
loadingInstance.close(); // 关闭
Message.error({
message: errorText
});
reject(e);
};

// 发送
if (data) x.send(data);
else x.send();
});
};

// blob数据转json处理函数
function blobToJson(blob) {
return new Promise((resolve, reject) => {
let reader = new FileReader();
reader.readAsText(blob);
reader.onload = e => {
let jsonRes = JSON.parse(e.target.result);
resolve(jsonRes);
};
});
}

// 文件名获取方式(来自自定义或响应头)
function getFileName( customFilename, xhr ){
let name = '';
if ( customFilename ){
name = customFilename
}else {
let headers = xhr.getResponseHeader('content-disposition') || moment( +new Date() ).format('YMDHmm'),
index = headers.search('filename='),
start = index === -1 ? 0 : index;

name = decodeURIComponent( headers.slice( start + 9 ) );
}

// 如果没有扩展名,默认加上 .xls
return name.lastIndexOf('.') !== -1 ? name : ( name + '.xls' );
}

export default download;

上方代码可直接应用到实际项目中,使用方式就是按照正常的 Ajax 请求那样调用此函数即可。

使用实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// foo.js

import download from './download.js'

download({
url: '/foo/bar/download'
data: {
fileId: 1
},
filename: '自定义文件名.xls',
handleErr( res ){ // 系统级抛错处理回调
console.log( res )
}
})
.then( () => {
// download success
// do something else ...
})
.catch(err => {
// download failed
// do something else ...
console.log( err )
})


说明

  • 如果想要自定义下载的文件名称,请始终保持你设置的 filename (必须包含文件名扩展后缀)。

  • 该方法仅支持 postget 请求。

Logs

  • 2018-12-1 优化下载方法。

  • 2019-3-1 新增 handleErr

  • 2019-5-27 去除 mimetype 参数,该参数无需传入。