原文连接javascript
在开发中遇到了须要实现文件下载的功能,起初觉得只用
<a>
标签就能搞定,<a>
标签确实可以搞定常见的场景。可是像导出或者在header
里面添加了特殊字段的时候,使用<a>
标签就搞不定了,又不想去使用原生XMLHttpRequest
,由于又一堆的兼容性需求(技术能力不够ε=ε=ε=┏(゜ロ゜;)┛,有现成的兼容方案为啥要本身造轮子呢,说不定还爆胎>逃666),因此萌生基于Axios
封装。前端
浏览器的GET(frame、a)和POST(form)请求具备以下特色:java
Ajax请求具备以下特色:ios
Ajax自己设计的目标就是用来获取文本数据的,而不是用来搞二进制的。git
最近看文档发现, xhr在老的浏览器里面也是能够发送二进制数据的,用到一个冷门的api
XMLHttpRequest#overrideMimeType
,能够看这里在老的浏览器中接受二进制数据,注意兼容性。github
XMLHttpRequest 2.0
新增的数据类型Blob
看张老师的文章 理解DOMString、Document、FormData、Blob、File、ArrayBuffer数据类型 json
有了Blob
类型以后,JavaScript处理二进制进一步加强,能够说之后想怎样就怎样(废话)。axios
服务端返回的头部须要设置api
Content-Disposition: "attachment; filename=xxxx.docx;"浏览器
<a>
标签的直接下载// Downloader.ts
import qs from 'qs';
/** * downloadByUrl * @param config - 配置参数 * @param config.url - 地址 * @param config.params - querystring参数. * @param filename 文件名称,包括扩展名部分(不必定生效) * * @description * 原理是使用<a>的href和download属性,因此filename不必定会生效, 浏览器机制问题. * * @see https://zhuanlan.zhihu.com/p/58888918 * @see https://github.com/kennethjiang/js-file-download */
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
的实现// Downloader.ts
import axios, { AxiosResponse, AxiosRequestConfig } from 'axios';
import fileDownload from 'js-file-download';
import logger from 'js-logger';
/** * 提取文件名. * @param response axios的response * @description 从reponse header的content-disposition中提取文件名. */
const extractFilenameFromResponseHeader = (response: AxiosResponse): string => {
// content-disposition: "attachment; filename=xxxx.docx;"
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] : '';
}
// 解码以前尝试去除空格和双引号
// content-disposition: "attachment; filename=\"xxxx.docx\";"
return decodeURIComponent(filename.trim().replace(new RegExp('"', 'g'), ''));
};
const axiosInstance = axios.create({/* 能够传递公共默认的axios配置,可是注意reponse interceptor中默认把reponse.data做为JSON解析的状况 */});
// https://www.zhihu.com/question/263323250
// https://github.com/axios/axios/issues/815#issuecomment-340972365
const downloadByAxios = async function ( config: AxiosRequestConfig, filename = '' ): Promise<any | AxiosResponse<any>> {
let response = await axiosInstance({
...config,
responseType: 'blob', // 指定类型
});
let resBlob = response.data; // <--- store the blob if it is
let respData = null;
// 若是肯定接口response.data是二进制,因此请求失败时是JSON.
// 这里只对response.data作JSON的尝试解析
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); // <--- try to parse as json evantually
} catch (err) {
// ignore
}
// 若是response.data可以肯定是二进制,则respData = null说明请求成功
// 不然 respData !== null说明请求失败
if (respData as ResponseData) {
logger.error(respData);
// 方便调用者有进一步的 then().catch()处理
return Promise.reject({
...respData,
});
} else {
// 触发浏览器下载
// 若是没有传递filename尝试从Content-Disposition提取
fileDownload(resBlob, filename || extractFilenameFromResponseHeader(
response
));
// 方便调用者有进一步的 then().catch()处理
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
默认配置须要从新配置一遍
js-file-download 理解DOMString、Document、FormData、Blob、File、ArrayBuffer数据类型
欢迎加入群聊
若是入群失败,添加我的微信,拉你入群,验证消息:前端交流
关注微信公众号,发现更多精彩内容。