在前端開發過程當中,咱們常常會遇到須要發送異步請求的狀況。而使用一個功能齊全,接口完善的HTTP請求庫,可以在很大程度上減小咱們的開發成本,提升咱們的開發效率。javascript
axios是一個在近些年來很是火的一個HTTP請求庫,目前在GitHub中已經擁有了超過40K的star,受到了各位大佬的推薦。前端
今天,咱們就來看下,axios究竟是如何設計的,其中又有哪些值得咱們學習的地方。我在寫這邊文章時,axios的版本爲0.18.0。咱們就以這個版本的代碼爲例,來進行具體的源碼閱讀和分析。當前axios全部源碼文件都在lib
文件夾中,所以咱們下文中提到的路徑均是指lib
文件夾中的路徑。java
本文的主要內容有:ios
想要了解axios的設計,咱們首先須要來看下axios是如何使用的。咱們經過一個簡單示例來介紹如下axios的API。git
axios({ method:'get', url:'http://bit.ly/2mTM3nY', responseType:'stream' }) .then(function(response) { response.data.pipe(fs.createWriteStream('ada_lovelace.jpg')) }); 複製代碼
這是一個官方的API示例。從上面的代碼中咱們能夠看到,axios的用法與jQuery的ajax很類似,都是經過返回一個Promise(也能夠經過success的callback,不過建議使用Promise或者await)來繼續後面的操做。github
這個代碼示例很簡單,我就不過多贅述了,下面讓咱們來看下如何添加一個過濾器函數。ajax
// 增長一個請求攔截器,注意是2個函數,一個處理成功,一個處理失敗,後面會說明這種狀況的緣由 axios.interceptors.request.use(function (config) { // 請求發送前處理 return config; }, function (error) { // 請求錯誤後處理 return Promise.reject(error); }); // 增長一個響應攔截器 axios.interceptors.response.use(function (response) { // 針對響應數據進行處理 return response; }, function (error) { // 響應錯誤後處理 return Promise.reject(error); }); 複製代碼
經過上面的示例咱們能夠知道:在請求發送前,咱們能夠針對請求的config參數進行數據處理;而在請求響應後,咱們也能針對返回的數據進行特定的操做。同時,在請求失敗和響應失敗時,咱們均可以進行特定的錯誤處理。axios
在完成搜索相關的功能時,咱們常常會須要頻繁的發送請求來進行數據查詢的狀況。一般來講,咱們在下一次請求發送時,就須要取消上一次請求。所以,取消請求相關的功能也是一個優勢。axios取消請求的示例代碼以下:promise
const CancelToken = axios.CancelToken; const source = CancelToken.source(); axios.get('/user/12345', { cancelToken: source.token }).catch(function(thrown) { if (axios.isCancel(thrown)) { console.log('Request canceled', thrown.message); } else { // handle error } }); axios.post('/user/12345', { name: 'new name' }, { cancelToken: source.token }) // cancel the request (the message parameter is optional) source.cancel('Operation canceled by the user.'); 複製代碼
經過上面的示例咱們能夠看到,axios使用的是基於CancelToken的一個撤回提案。不過,目前該提案已經被撤回,具體詳情能夠見此處。具體的撤回實現方法咱們會在後面的章節源碼分析的時候進行說明。瀏覽器
經過上面的例子,我相信你們對axios的使用方法都有了一個大體的瞭解。下面,咱們將按照模塊來對axios的設計與實現進行分析。下圖是咱們在這篇博客中將會涉及到的相關的axios的文件,若是讀者有興趣的話,能夠經過clone相關代碼結合博客進行閱讀,這樣可以加深對相關模塊的理解。
做爲核心模塊,axios發送請求相關的代碼位於core/dispatchReqeust.js
文件中。因爲篇幅有限,下面我選取部分重點的源碼進行簡單的介紹:
module.exports = function dispatchRequest(config) { throwIfCancellationRequested(config); // 其餘源碼 // default adapter是一個能夠判斷當前環境來選擇使用Node仍是XHR進行請求發送的模塊 var adapter = config.adapter || defaults.adapter; return adapter(config).then(function onAdapterResolution(response) { throwIfCancellationRequested(config); // 其餘源碼 return response; }, function onAdapterRejection(reason) { if (!isCancel(reason)) { throwIfCancellationRequested(config); // 其餘源碼 return Promise.reject(reason); }); }; 複製代碼
經過上面的代碼和示例咱們能夠知道,dispatchRequest
方法是經過獲取config.adapter
來獲得發送請求的模塊的,咱們本身也能夠經過傳入符合規範的adapter函數來替換掉原生的模塊(雖然通常不會這麼作,不過也算是一個鬆耦合擴展點)。
在default.js
文件中,咱們可以看到相關的adapter選擇邏輯,即根據當前容器中特有的一些屬性和構造函數來進行判斷。
function getDefaultAdapter() { var adapter; // 只有Node.js纔有變量類型爲process的類 if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') { // Node.js請求模塊 adapter = require('./adapters/http'); } else if (typeof XMLHttpRequest !== 'undefined') { // 瀏覽器請求模塊 adapter = require('./adapters/xhr'); } return adapter; } 複製代碼
axios中XHR模塊較爲簡單,爲XMLHTTPRequest對象的封裝,咱們在這裏就不過多進行介紹了,有興趣的同窗能夠自行閱讀,代碼位於adapters/xhr.js
文件中。
瞭解了dispatchRequest
實現的HTTP請求發送模塊,咱們來看下axios是如何處理請求和響應攔截函數的。讓咱們看下axios中請求的統一入口request
函數。
Axios.prototype.request = function request(config) { // 其餘代碼 var chain = [dispatchRequest, undefined]; var promise = Promise.resolve(config); this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { chain.unshift(interceptor.fulfilled, interceptor.rejected); }); this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { chain.push(interceptor.fulfilled, interceptor.rejected); }); while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); } return promise; }; 複製代碼
這個函數是axios發送請求的入口,由於函數實現比較長,我就簡單說一下相關的設計思路:
dispatchReqeust
和與之對應的undefined
。後面須要增長一個undefined
是由於在Promise中,須要一個success和一個fail的回調函數,這個從代碼promise = promise.then(chain.shift(), chain.shift());
就可以看出來。所以,dispatchReqeust
和undefined
咱們能夠成爲一對函數。dispatchReqeust
是處於中間的位置。它的前面是請求攔截器,經過unshift
方法放入;它的後面是響應攔截器,經過push
放入。要注意的是,這些函數都是成對的放入,也就是一次放入兩個。經過上面的request
代碼,咱們大體知道了攔截器的使用方法。接下來,咱們來看下如何取消一個HTTP請求。
取消請求相關的模塊在Cancel/
文件夾中。讓咱們來看下相關的重點代碼。
首先,讓咱們來看下元數據Cancel
類。它是用來記錄取消狀態一個類,具體代碼以下:
function Cancel(message) { this.message = message; } Cancel.prototype.toString = function toString() { return 'Cancel' + (this.message ? ': ' + this.message : ''); }; Cancel.prototype.__CANCEL__ = true; 複製代碼
而在CancelToken類中,它經過傳遞一個Promise的方法來實現了HTTP請求取消,然咱們看下具體的代碼:
function CancelToken(executor) { if (typeof executor !== 'function') { throw new TypeError('executor must be a function.'); } var resolvePromise; this.promise = new Promise(function promiseExecutor(resolve) { resolvePromise = resolve; }); var token = this; executor(function cancel(message) { if (token.reason) { // Cancellation has already been requested return; } token.reason = new Cancel(message); resolvePromise(token.reason); }); } CancelToken.source = function source() { var cancel; var token = new CancelToken(function executor(c) { cancel = c; }); return { token: token, cancel: cancel }; }; 複製代碼
而在adapter/xhr.js
文件中,有與之相對應的取消請求的代碼:
if (config.cancelToken) { // 等待取消 config.cancelToken.promise.then(function onCanceled(cancel) { if (!request) { return; } request.abort(); reject(cancel); // 重置請求 request = null; }); } 複製代碼
結合上面的取消HTTP請求的示例和這些代碼,咱們來簡單說下相關的實現邏輯:
CancelToken
類的實例A和一個函數cancel。request.abort()
。在以前的章節中有提到過,axios在處理髮送請求的dispatchRequest
函數時,沒有當作一個特殊的函數來對待,而是採用一視同仁的方法,將其放在隊列的中間位置,從而保證了隊列處理的一致性,提升了代碼的可閱讀性。
在adapter的處理邏輯中,axios沒有把http和xhr兩個模塊(一個用於Node.js發送請求,另外一個則用於瀏覽器端發送請求)當成自身的模塊直接在dispatchRequest
中直接飲用,而是經過配置的方法在default.js
文件中進行默認引入。這樣既保證了兩個模塊間的低耦合性,同時又可以爲從此用戶須要自定義請求發送模塊保留了餘地。
在取消HTTP請求的邏輯中,axios巧妙的使用了一個Promise來做爲觸發器,將resolve函數經過callback中參數的形式傳遞到了外部。這樣既可以保證內部邏輯的連貫性,也可以保證在須要進行取消請求時,不須要直接進行相關類的示例數據改動,最大程度上避免了侵入其餘的模塊。
本文對axios相關的使用方式、設計思路和實現方法進行了詳細的介紹。讀者可以經過上述文章,瞭解axios的設計思想,同時可以在axios的代碼中,學習到關於模塊封裝和交互等相關的經驗。
因爲篇幅緣由,本文僅針對axios的核心模塊進行了分解和介紹,若是對其餘代碼有興趣的同窗,能夠去GitHub進行查看。
若是有任何疑問或者觀點,歡迎隨時留言討論。