axios是一個基於Promise的http請求庫,可用於瀏覽器和 Node。能夠說是目前最爲經常使用的http庫,有必要了解一下其內部的實現原理node
const axios = require('axios');
axios.defaults.baseURL = 'http://xxx.com/api';
axios.interceptors.request.use(resolveFn1, rejectFn2); // 添加請求攔截器
axios.interceptors.response.use(resolveFn2, rejectFn2); // 添加響應攔截器
axios.get('/get').then(() => {
// 請求成功的處理
}, () => {
// 請求異常的處理
}
);
複製代碼
上述代碼演示瞭如何發起axios請求,先從require('axios')
提及。 require('axios')
導出值的來自./lib/axios.js
,而./lib/axios.js
導出是內部調用createInstance以後的返回值。createInstance方法會返回一個axios實例
(注:axios.create也能夠建立axios實例
,其內部也是調用createInstance)。咱們先來看下createInstance的源碼是如何實現的ios
// ./lib/axios.js
function createInstance(defaultConfig) {
// 根據默認設置 新建一個Axios對象
var context = new Axios(defaultConfig);
// axios中全部的請求[axios, axios.get, axios.post等...]內部調用的都是Axios.prototype.request,見[./code/Axios.js]
// 將Axios.prototype.request的內部this綁定到新建的Axios對象上,從而造成一個axios實例
var instance = bind(Axios.prototype.request, context);
utils.extend(instance, Axios.prototype, context); // 將Axios.prototype屬性添加到instance上,若是屬性爲函數則綁定this爲context後再添加
utils.extend(instance, context); // 將新建的Axios對象屬性添加到instance,同上
return instance;
}
複製代碼
首先內部會新建一個Axios對象,Axios結構函數以下git
function Axios(instanceConfig) {
this.defaults = instanceConfig; // 一些默認設置項
this.interceptors = {
request: new InterceptorManager(), // request攔截器
response: new InterceptorManager() // response攔截器
};
}
複製代碼
新建的Axios對象主要是用來掛載axios實例的一些設置(如defaults會掛載axios實例的通用設置,interceptors用於存放攔截器)github
根據源碼可知,axios實例(instance)是對Axios.prototype.request方法
包裹了一層函數,主要是爲將Axios.prototype.request內部的this綁定到新建的Axios對象上。而後經過 utils.extend 將內部context和Axios.prototyp的屬性添加到這個Axios.prototype.request
方法上,添加上去的函數也會綁定this到新建的Axios對象上。最終的axios實例上面的方法內部的this指向的都是新建的Axios對象,從而使得不一樣axios實例之間隔離了做用域,能夠對每一個axios實例設置不一樣的configajax
爲何不將全部方法在Axios上實現而後返回new Axios呢?axios
由於axios內部調用的都是Axios.prototype.request方法,Axios.prototype.request默認請求方法爲get。爲了讓開發者能夠直接axios(config)就能夠發送get請求,而不須要axios.get(config)。若是直接new一個Axios對象是沒法實現這種簡寫的(沒錯,就是爲了少打幾個字)api
實際上axios.post、axios.put等全部axios的請求方法內部都是調用Axios.prototype.request數組
// 見./lib/core/Axios
Axios.prototype.request = function request(config) {
if (typeof config === 'string') {
config = utils.merge({
url: arguments[0]
}, arguments[1]);
}
// 進行配置項的合併 優先級: Axios默認的defaults < Axios.defaults < 調用時axios請求方法時傳入的config
config = utils.merge(defaults, {
method: 'get' // 默認爲get方法
}, this.defaults, config);
config.method = config.method.toLowerCase();
var chain = [dispatchRequest, undefined]; // dispatchRequest封裝了對於發起ajax的邏輯處理
var promise = Promise.resolve(config);
// request攔截器的執行順序是: 先加入後執行
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// 而response攔截器則是: 先加入的先執行
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
/* 假如咱們分別添加了2個request和respnse攔截器, 那麼最終執行順序以下: request.interceptor2 => request.interceptor1 => [dispatchRequest, undefined] => response.interceptor1 => response.interceptor2 內部經過promise.then造成promise鏈, 從而將chain中攔截器的調用串聯起來, dispatchRequest是對於ajax請求發起的封裝實現,也會返回一個Promise對象 */
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
複製代碼
Axios.protytype.request
內部會進行了一些配置項的合併工做,變量chain至關於一個任務隊列,以2個爲一組存聽任務(1個是任務成功回調,1個是任務失敗回調),經過不斷調用promise.then方法造成一個promise鏈,從而將全部的任務執行串聯起來。promise
有一點須要注意是攔截器的執行順序,request 攔截器先加入的後執行,response 攔截器則是先加入的先執行。瀏覽器
執行順序示例:request.interceptor2 => request.interceptor1 => [dispatchRequest, undefined] => response.interceptor1 => response.interceptor2
複製代碼
request.interceptor用於請求發起前的準備工做(能夠修改data和headers),response.interceptor用於服務器返回數據以後的處理工做(也是對data進行處理),整個請求過程的發起過程是經過dispatchRequest實現的
// 省略部分代碼,詳細代碼見./lib/code/dispatchRequest
function dispatchRequest(config) {
// ...
// 依次調用transformRequest數組中的函數對data,headers進行處理,方便在向服務器發送請求以前對data和headers進行修改(例如對data進行編碼加密等)
config.data = transformData(
config.data,
config.headers,
config.transformRequest
);
// ...
return adapter(config).then(
function onAdapterResolution(response) {
// 判斷請求是否被取消,若是請求已經被手動取消則會拋出一個異常
throwIfCancellationRequested(config);
// Transform response data
// 利用transformResponse對服務器返回的data進行處理
response.data = transformData(response.data, response.headers, config.transformResponse);
return response;
},
function onAdapterRejection(reason) {
// 執行到這裏說明請求出現了異常(代碼執行出錯或者狀態碼錯誤等),可是若是這是執行取消請求操做,那麼最終的異常信息仍是取消請求所拋出的異常,這樣是爲了當開發者手動取消請求時,能夠對全部取消請求進行統一的後續處理
if (!isCancel(reason)) {
throwIfCancellationRequested(config);
// Transform response data
if (reason && reason.response) {
reason.response.data = transformData(
reason.response.data,
reason.response.headers,
config.transformResponse
);
}
}
return Promise.reject(reason);
}
);
}
複製代碼
transformData(config.data, config.headers, config.transformRequest)
是爲了向服務器發送請前對 data 進行處理,能夠經過設置transformRequest對data和header進行修改,通常進行加密編碼等操做。
adapter 是一個典型的適配器模式的實現, 其默認值爲getDefaultAdapter的返回值
// 見./lib/cord/defaults.js
// 根據當前執行環境(瀏覽器 or Node)執行相應的請求發起邏輯
function getDefaultAdapter() {
var adapter;
if (typeof XMLHttpRequest !== 'undefined') { // 瀏覽器環境
// For browsers use XHR adapter
adapter = require('./adapters/xhr');
} else if (typeof process !== 'undefined') { // node環境
// For node use HTTP adapter
adapter = require('./adapters/http');
}
return adapter;
}
複製代碼
能夠看到adapter內部對不一樣環境作了適配處理,封裝了統一的行爲: 根據config發送請求而後返回一個promise,promise的狀態根據請求的結果來決定。 各個環境具體的實現,可自行閱讀源碼
接下來咱們來看下adapter返回promise的成功和失敗回調是如何處理的
至此 axios 庫的處理流程就結束了。
本文大體講了一下axios的執行原理,還有不少細節沒有涉及,更多見源碼註釋 因爲以前沒有使用過axios╮(╯▽╰)╭ ,若有不對之處請多多指教!!!