原文連接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數據類型
歡迎加入羣聊
若是入羣失敗,添加我的微信,拉你入羣,驗證消息:前端交流
關注微信公衆號,發現更多精彩內容。