axios
实现文件下载功能
在开发中遇到了需要实现文件下载的功能,起初以为只用<a>
标签就能搞定,<a>
标签确实能够搞定常见的场景。但是像导出或者在header
里面添加了特殊字段的时候,使用<a>
标签就搞不定了,又不想去使用原生XMLHttpRequest
,因为又一堆的兼容性需求(技术能力不够ε=ε=ε=┏(゜ロ゜;)┛,有现成的兼容方案为啥要自己造轮子呢,说不定还爆胎>逃666),所以萌生基于Axios
封装。
Ajax无法下载文件的原因
浏览器的GET(frame、a)和POST(form)请求具有如下特点:
- response会交由浏览器处理
- response内容可以为二进制文件、字符串等
Ajax请求具有如下特点:
- response会交由Javascript处理
- response内容仅可以为字符串
Ajax本身设计的目标就是用来获取文本数据的,而不是用来搞二进制的。
XMLHttpRequest 2.0
新增的数据类型Blob
看张老师的文章 理解DOMString、Document、FormData、Blob、File、ArrayBuffer数据类型
有了Blob
类型之后,JavaScript处理二进制进一步增强,可以说以后想怎样就怎样(废话)。
文件下载实现
服务端返回的头部需要设置
Content-Disposition: “attachment; filename=xxxx.docx;”
<a>
标签的直接下载
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
| import qs from 'qs';
export function downloadByUrl( config: { url: string; params: any; }, filename = '' ): void { var tempLink = document.createElement('a'); tempLink.style.display = 'none'; tempLink.href = config.url + qs.stringify(config.params, { addQueryPrefix: true }); tempLink.setAttribute('download', filename); if (typeof tempLink.download === 'undefined') { tempLink.setAttribute('target', '_blank'); }
document.body.appendChild(tempLink); tempLink.click(); document.body.removeChild(tempLink); }
|
主要是使用了js-file-download
的代码,进行了简单的封装,而且去除了对Blob
的依赖,主要为了兼容低版本的浏览器。同时使用了qs
对querystring
参数进行了简单的处理。
基于axios
的实现
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
| import axios, { AxiosResponse, AxiosRequestConfig } from 'axios'; import fileDownload from 'js-file-download'; import logger from 'js-logger';
const extractFilenameFromResponseHeader = (response: AxiosResponse): string => { const contentDisposition = response.headers['content-disposition']; const patt = new RegExp('filename=([^;]+\\.[^\\.;]+);*'); const result = patt.exec(contentDisposition) as RegExpExecArray; let filename = '';
if (result) { filename = result.length > 0 ? result[1] : ''; } return decodeURIComponent(filename.trim().replace(new RegExp('"', 'g'), '')); };
const axiosInstance = axios.create({
});
const downloadByAxios = async function ( config: AxiosRequestConfig, filename = '' ): Promise<any | AxiosResponse<any>> { let response = await axiosInstance({ ...config, responseType: 'blob', });
let resBlob = response.data; let respData = null;
try { let respText = await new Promise((resolve, reject) => { let reader = new FileReader(); reader.addEventListener('abort', reject); reader.addEventListener('error', reject); reader.addEventListener('loadend', () => { resolve(reader.result as string); }); reader.readAsText(resBlob); }); respData = JSON.parse(respText as string); } catch (err) { } if (respData as ResponseData) { logger.error(respData); return Promise.reject({ ...respData, }); } else { fileDownload(resBlob, filename || extractFilenameFromResponseHeader( response )); return Promise.resolve({ ...response, }); } };
|
代码大部分都是参考这个issue实现的,只有少部分的个人代码。
基于axios
实现的功能:
- 可以使用
axios
的所有参数,不管请求是GET
或者POST
- 解决了在
header
中添加额外的参数的需求
- 可以指定
filename
,如果服务端没有设置content-disposition
的情况
- 返回
Promise
方便调用者进一步处理请求
缺点:
只能使用独立的axios
实例,不能公用一个axios
本来想把下载功能使用axios interceptor
拦截器实现,但是返回的response.data
是Blob
二进制,但是其它的response interceptor
默认前提都是把response.data
当作JSON
处理,导致全部出现异常,所以把下载功能独立出来,更方便维护。
- 使用独立的
axios
实例,所以项目中的axios
默认配置需要重新配置一遍
参考链接
Content-Disposition
axios.js实现下载功能
axios.js #815实现
StreamSaver
FileSaver
js-file-download
理解DOMString、Document、FormData、Blob、File、ArrayBuffer数据类型