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数据类型