項目主要技術棧是Vue,因此咱們在請求後端接口的時候通常會用到axios的組件,那麼咱們就來分析一下它的源碼及它的實現。javascript
axios主要目錄結構
├── /dist/ # 項目輸出目錄 ├── /lib/ # 項目源碼目錄 │ ├── /cancel/ # 定義取消功能 │ ├── /core/ # 一些核心功能 │ │ ├── Axios.js # axios的核心主類---------------------------這是其最核心部分 │ │ ├── dispatchRequest.js # 用來調用http請求適配器方法發送請求 | │ │ ├── InterceptorManager.js # 攔截器構造函數 | │ │ └── settle.js # 根據http響應狀態,改變Promise的狀態-------- │ ├── /helpers/ # 一些輔助方法 │ ├── /adapters/ # 定義請求的適配器 xhr、http----這個文件夾封裝了ajax的請求 │ │ ├── http.js # 實現http適配器 │ │ └── xhr.js # 實現xhr適配器 │ ├── axios.js # 對外暴露接口 │ ├── defaults.js # 默認配置 │ └── utils.js # 公用工具 ├── package.json # 項目信息 ├── index.d.ts # 配置TypeScript的聲明文件 └── index.js # 入口文件
由使用到探索
咱們通常使用的時候都會先new一個axios實例出來,那麼這個實例是怎麼來的呢?裏邊都有什麼呢?咱們來看一下源代碼最外層一個axios.js文件java
function createInstance(defaultConfig) { var context = new Axios(defaultConfig); var instance = bind(Axios.prototype.request, context); // Copy axios.prototype to instance utils.extend(instance, Axios.prototype, context); // Copy context to instance utils.extend(instance, context); return instance; } // Create the default instance to be exported var axios = createInstance(defaults); // Expose Axios class to allow class inheritance axios.Axios = Axios; // Factory for creating new instances axios.create = function create(instanceConfig) { return createInstance(mergeConfig(axios.defaults, instanceConfig)); };
經過源代碼咱們能夠看到axios導出的axios對象中有一個create方法,還有個Axios對象,同時axios自己仍是createInstance函數,而且他們都會傳遞一個參數進去,因此咱們在使用axios的時候會有多種不一樣的調用方法,他們最終實例化的實際上是core文件下的Axios.js中所寫的Axios構造函數。ios
// /lib/core/Axios.js function Axios(instanceConfig) { this.defaults = instanceConfig; this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager() }; } Axios.prototype.request = function request(config) { // ...省略代碼 }; // 爲支持的請求方法提供別名 utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) { Axios.prototype[method] = function(url, config) { return this.request(utils.merge(config || {}, { method: method, url: url })); }; }); utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { Axios.prototype[method] = function(url, data, config) { return this.request(utils.merge(config || {}, { method: method, url: url, data: data })); }; });
上邊是部分源代碼,咱們能夠看到在這個構造函數中他會有個default對象,是用來存放咱們的配置信息的,還有個interceptors對象,裏邊包了兩個實例,分別對應了request攔截器和response攔截器。而且在Axios的原型鏈上有寫了一個request方法,並對支持的請求方法都賦值了一個別名。ajax
咱們的配置config是如何生效的
咱們看源代碼會發現幾乎每一個方法都會有一個config參數,咱們使用的時候的config就是經過這種方式貫穿整個axios的。咱們來看一下咱們使用的3種方式:json
import axios from 'axios' // 第1種:直接修改Axios實例上defaults屬性,主要用來設置通用配置 axios.defaults[configName] = value; // 第2種:發起請求時最終會調用Axios.prototype.request方法,而後傳入配置項,主要用來設置「個例」配置 axios({ url, method, headers, }); // 第3種:新建一個Axios實例,傳入配置項,此處設置的是通用配置 let newAxiosInstance = axios.create({ [configName]: value, });
對比咱們剛纔所說的axios實例的生成,咱們能夠看到這三種方式其實都會把config用過參數的形式傳遞進去,而且在每一步調用的時候生效。axios
請求攔截器和響應攔截器
咱們在上邊看axios的構造函數的時候看到他綁定了一個request攔截器和response攔截器,那麼他們是作什麼的呢?又是如何生效的呢?顧名思義,攔截器就是攔截請求的,能夠在發送request以前或者接收response以前進行攔截,而後作一些事情達到咱們的目的。後端
// /lib/core/InterceptorManager.js function InterceptorManager() { this.handlers = []; // 存放攔截器方法,數組內每一項都是有兩個屬性的對象,兩個屬性分別對應成功和失敗後執行的函數。 } // 往攔截器裏添加攔截方法 InterceptorManager.prototype.use = function use(fulfilled, rejected) { this.handlers.push({ fulfilled: fulfilled, rejected: rejected }); return this.handlers.length - 1; }; // 用來註銷指定的攔截器 InterceptorManager.prototype.eject = function eject(id) { if (this.handlers[id]) { this.handlers[id] = null; } }; // 遍歷this.handlers,並將this.handlers裏的每一項做爲參數傳給fn執行 InterceptorManager.prototype.forEach = function forEach(fn) { utils.forEach(this.handlers, function forEachHandler(h) { if (h !== null) { fn(h); } }); };
// /lib/core/Axios.js Axios.prototype.request = function request(config) { // ... var chain = [dispatchRequest, undefined]; // 初始化一個promise對象,狀態微resolved,接收到的參數微config對象 var promise = Promise.resolve(config); // 注意:interceptor.fulfilled 或 interceptor.rejected 是可能爲undefined 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); }); // 添加了攔截器後的chain數組大概會是這樣的: // [ // requestFulfilledFn, requestRejectedFn, ..., // dispatchRequest, undefined, // responseFulfilledFn, responseRejectedFn, ...., // ] // 只要chain數組長度不爲0,就一直執行while循環 while (chain.length) { // 數組的 shift() 方法用於把數組的第一個元素從其中刪除,並返回第一個元素的值。 // 每次執行while循環,從chain數組裏按序取出兩項,並分別做爲promise.then方法的第一個和第二個參數 // 按照咱們使用InterceptorManager.prototype.use添加攔截器的規則,正好每次添加的就是咱們經過InterceptorManager.prototype.use方法添加的成功和失敗回調 // 經過InterceptorManager.prototype.use往攔截器數組裏添加攔截器時使用的數組的push方法, // 對於請求攔截器,從攔截器數組按序讀到後是經過unshift方法往chain數組數裏添加的,又經過shift方法從chain數組裏取出的,因此得出結論:對於請求攔截器,先添加的攔截器會後執行 // 對於響應攔截器,從攔截器數組按序讀到後是經過push方法往chain數組裏添加的,又經過shift方法從chain數組裏取出的,因此得出結論:對於響應攔截器,添加的攔截器先執行 // 第一個請求攔截器的fulfilled函數會接收到promise對象初始化時傳入的config對象,而請求攔截器又規定用戶寫的fulfilled函數必須返回一個config對象,因此經過promise實現鏈式調用時,每一個請求攔截器的fulfilled函數都會接收到一個config對象 // 第一個響應攔截器的fulfilled函數會接受到dispatchRequest(也就是咱們的請求方法)請求到的數據(也就是response對象),而響應攔截器又規定用戶寫的fulfilled函數必須返回一個response對象,因此經過promise實現鏈式調用時,每一個響應攔截器的fulfilled函數都會接收到一個response對象 // 任何一個攔截器的拋出的錯誤,都會被下一個攔截器的rejected函數收到,因此dispatchRequest拋出的錯誤纔會被響應攔截器接收到。 // 由於axios是經過promise實現的鏈式調用,因此咱們能夠在攔截器裏進行異步操做,而攔截器的執行順序仍是會按照咱們上面說的順序執行,也就是 dispatchRequest 方法必定會等待全部的請求攔截器執行完後再開始執行,響應攔截器必定會等待 dispatchRequest 執行完後再開始執行。 promise = promise.then(chain.shift(), chain.shift()); } return promise; };
dispatchrequest作了什麼事情
咱們再看攔截器生效的時候發如今Axios.prototype.request
中會有這麼一段代碼var chain = [dispatchRequest, undefined];
,而且會在promise = promise.then(chain.shift(), chain.shift());
被調用,那麼這個dispatchrequest是什麼呢?數組
// /lib/core/dispatchRequest.js module.exports = function dispatchRequest(config) { throwIfCancellationRequested(config); // Support baseURL config if (config.baseURL && !isAbsoluteURL(config.url)) { config.url = combineURLs(config.baseURL, config.url); } // Ensure headers exist config.headers = config.headers || {}; // 對請求data進行轉換 config.data = transformData( config.data, config.headers, config.transformRequest ); // 對header進行合併處理 config.headers = utils.merge( config.headers.common || {}, config.headers[config.method] || {}, config.headers || {} ); // 刪除header屬性裏無用的屬性 utils.forEach( ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'], function cleanHeaderConfig(method) { delete config.headers[method]; } ); // http請求適配器會優先使用config上自定義的適配器,沒有配置時纔會使用默認的XHR或http適配器,不過大部分時候,axios提供的默認適配器是可以知足咱們的 var adapter = config.adapter || defaults.adapter; return adapter(config).then(/**/); };
經過源碼分析,咱們能夠看到dispatchrequest主要作了3件事情:promise
-
拿到config對象,對config進行傳給http請求適配器前的最後處理;異步
-
http請求適配器根據config配置,發起請求 ;
-
http請求適配器請求完成後,若是成功則根據header、data、和config.transformResponse拿到數據,
轉換後的response,並return。
段落小結
經過上面對axios源碼結構分析,咱們能夠獲得axios搭建的一個主要結構:
調用===>Axios.prototype.request===>請求攔截器interceptors===>dispatchRequest===>請求轉換器transformRequest===>請求適配器xhrAdapter===>響應轉換器transformResponse===>響應攔截器interceptors
axios如何基於promise搭建異步橋樑
瞭解了axios的主要結構及調用順序,那咱們來看一下axios是如何經過promise來搭建異步的橋樑?
axios.get(/**/) .then(data => { // 此處能夠拿到向服務端請求回的數據 }); .catch(error => { // 此處能夠拿到請求失敗或取消或其餘處理失敗的錯誤對象 });
咱們那get方法來舉例,當咱們調用axios的get方法的時候其實是調用axios原型鏈上的get方法,而其原型鏈上的get方法又指向了其原型鏈上的request方法。
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) { Axios.prototype[method] = function(url, config) { return this.request(utils.merge(config || {}, { method: method, url: url })); }; });
Axios.prototype.request = function request(config) { // ... var chain = [dispatchRequest, undefined]; // 將config對象看成參數傳給Primise.resolve方法 var promise = Promise.resolve(config); while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); } return promise; };
咱們能夠看到在request方法裏邊有個chain數組,這個實際上是至關於一個方法的隊列,這裏會把request的攔截器插入chain數組的前邊,response攔截器插入chain數組後邊,經過下邊的while循環,兩兩調用promise.then來順序執行請求攔截器到dispatchrequest再到響應攔截器的方法。
在dispatchrequest中呢又會調用封裝好的ajax(xhrAdapter方法),xhrAdapter
方法返回的是還一個Promise對象
// /lib/adapters/xhr.js function xhrAdapter(config) { return new Promise(function dispatchXhrRequest(resolve, reject) { // ... 省略代碼 }); };
xhrAdapter
內的XHR發送請求成功後會執行這個Promise對象的resolve
方法,並將請求的數據傳出去, 反之則執行reject
方法,並將錯誤信息做爲參數傳出去。