axios深度解析(源碼)
文章目錄
axios的作用
支持請求/響應攔截器
支持取消請求
請求/響應數據轉換
批量發送多個請求
axios基本使用
npm install axios
該文件 在axios目錄下的lib下的axios.js
博客的目的通過對源碼的解析來完成對axios的執行流程有一個深入的瞭解。
先說一下axios常用語法
axios(config):通用/最本質的發任意類型請求的方式
axios(ur1[, config]): 可以只指定ur1發get請求
axios.request(config):等同於axios(config)
axios.get(ur1[, config]): 發get請求
axios.delete(url[, config]): 發delete請 求
axios.post(ur1[, data, config]): 發post請求
axios.put(url[, data, config]): 發put請求
axios.defaults . xxx:請求的默認全局配置
axios.interceptors.request.use():添加請求攔截器
axios.interceptors.response.use():添加響應攔截器
axios.create( [config]):創建個 新的axios(它沒有 下面的功能)
axios.Cancel():用於創建取消請求的錯誤對象
axios.CancelToken():用於創建取消請求的token對象
axios.isCancel():是否是一個取消請求的錯誤
axios.all(promises):用於批量執行多個異步請求
axios.spread():用來指定接收所有成功數據的回調函數的方法
其實上面這一些用法有一個核心的代碼就是request機制,理解了源碼中的request機制會對axios有更全面的認知。
axios入口文件核心代碼
首先我們先npm install axios
下載下來這個包,然後進入axios目錄下的lib中的axios.js文件來看下面的核心代碼。
axios入口文件
var utils = require('./utils'); //下面代碼中拷貝的模塊
var bind = require('./helpers/bind'); // bind模塊 修改this
var Axios = require('./core/Axios'); //Axios核心模塊
var mergeConfig = require('./core/mergeConfig'); // 合併模塊
var defaults = require('./defaults'); //默認配置模塊
//這個defaults默認有很多屬性,這些屬性就是在defaults.js中可以找到
首先 它引入 了許多東西,分別是一些功能函數和一些配置,這裏重點關注一下Axios和defaults模塊。
// axios和axios.create()直接調用這個函數
function createInstance(defaultConfig) {
//創建一個Axios的實例,傳入這個配置對象,這個Axios由於是Axios模塊中,則會在下面代碼中展示出來並分析。
var context = new Axios(defaultConfig);
//等同於Axios.prototype.request.bind(context)
//把this指定成Axios的實例就能使用Axios上的屬性了,使內部使用的this正確。
var instance = bind(Axios.prototype.request, context);
// Copy axios.prototype to instance
//擴展 將第二個Axios原型上的所有東西都拷貝到instance上 如: Axios.prototype.geturi ,Axios.prototype.get 等方法
utils.extend(instance, Axios.prototype, context);
// Copy context to instance 又把實例上的所有東西copy到instance上, 實例上有defaults和interceptors,代碼在下面Axios核心模塊中
utils.extend(instance, context);
return instance;
//經過這一大通的拷貝 他的作用是什麼呢? 爲什麼不直接使用實例返回呢? 因爲這樣的話他就不能axios()直接這樣加括號執行了。
// 所以這個函數的返回值本質上就是 Axios.prototype.request, 這個會在下面詳細講解這個request
}
//我們的axios直接調用的是這個createInstance函數
var axios = createInstance(defaults);
// Expose Axios class to allow class inheritance
axios.Axios = Axios;
// Factory for creating new instances
axios.create = function create(instanceConfig) {
//這個傳入的參數 mergeConfig 拿着axios默認配置和 一個新的配置(instanceConfig)合併成一個新的配置
//這個就是爲什麼有多個後臺接口就可以使用 這個方法 他這個配置是獨立的
//和axios的區別 create這個沒有後面添加的cancel/cancelToken等
return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
// axios和axios.create的功能基本一致 由此可見調用axios和使用create方法創建本質上都是調用的createInstance方法
// Expose Cancel & CancelToken 下面這些屬性都是 instance 沒有的。
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
// Expose all/spread
axios.all = function all(promises) {
return Promise.all(promises);
};
axios.spread = require('./helpers/spread');
module.exports = axios;
// Allow use of default import syntax in TypeScript
module.exports.default = axios;
Axios核心模塊
Axios構造函數
//這裏只寫出了 Axios構造函數 詳細內容在 下面request中
function Axios(instanceConfig) {
//把配置對象添加到 這個defaults屬性上
this.defaults = instanceConfig;
//將包含請求/響應攔截器 的對象保存到interceptors上
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
//中間我省略了中間代碼,由於這個函數尤其重要 我放到下面interceptors中單獨講解。
Axios.prototype.request = function request(config){
//此間省略大量代碼
}
Axios.prototype.getUri = function getUri(config) {
config = mergeConfig(this.defaults, config);
return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, '');
};
// 下面代碼把這個發請求的方法都綁定到Axios.prototype上
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url
}));
};
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, data, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url,
data: data
}));
};
});
defaults模塊:
如下部分代碼可以看出爲什麼defaults中爲什麼會有那麼多的默認配置。讓然還有很多屬性沒有列出,有興趣可以自己看一下。
utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) {
defaults.headers[method] = {};
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE);
});
request和 interceptors (攔截器)
Axios核心模塊
request整體流程 前面的話都是一些配置的處理,可以不用太關注
// axios()和axios.request()這兩種寫法是一樣的,這些方法目前還在Axios的原型上
Axios.prototype.request = function request(config) {
//用於發請求的函數
/*eslint no-param-reassign:0*/
// Allow for axios('example/url'[, config]) a la fetch API
if (typeof config === 'string') {
config = arguments[1] || {};
config.url = arguments[0];
} else {
config = config || {};
}
config = mergeConfig(this.defaults, config);
// Set config.method
if (config.method) {
config.method = config.method.toLowerCase();
} else if (this.defaults.method) {
config.method = this.defaults.method.toLowerCase();
} else {
config.method = 'get';
}
// Hook up interceptors middleware 下面是重點 (圈起來 要考!)
// 有一個數組數組的左邊放請求攔截器,數組的右邊放響應攔截器,中間這個dispatchRequest是用來發請求的函數,詳細分析如 圖一:
var chain = [dispatchRequest, undefined];
//成功的promise 值就是這個config
var promise = Promise.resolve(config);
//unshift
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// push
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
//
while (chain.length) {
//重點 (圈起來 要考) 這一步的作用就是 在圖二中把 數組的第一項,和第二項(攔截器成功和失敗回調) 取出 ,作爲這個promise成功的回調和失敗的回調,然後循環遍歷這個數組,取出所有的請求攔截器
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
圖一:
有多個請求攔截器, 請求攔截器執行和 添加請求攔截器的順序是相反的,就比如你添加了兩個請求攔截器,後添加的那一個先執行,請求攔截器 執行完之後 ,就會發請求,然後會有響應結果,有了響應結果後 執行響應攔截器,執行完響應攔截器 然後 就是執行 成功和失敗的回調 .then()方法。
圖二:
能堅持到這裏的小夥伴 已經很不容易了 可能有小夥伴不太理解這個 dispatch Request 怎麼發的請求 ,同樣我們進入這個模塊–擼源碼。
dispatchRequest模塊和adapters
module.exports = function dispatchRequest(config) {
// Transform request data 數據的轉換 json處理
config.data = transformData(
config.data,
config.headers,
config.transformRequest
);
//省略很多代碼
var adapter = config.adapter || defaults.adapter;
return adapter(config).then(function onAdapterResolution(response) {
//省略很多代碼
//它返回的也是一個promise ,我們需要知道這個adapter 最終找的是誰?
}
}
我們需要知道這個adapter 最終找的是誰?在lib下有一個爲adapters的目錄 其中有http.js和xhr.js.我們發的ajax用的就是這個xhr模塊 ,在這個模塊中 有一個xhrAdapter(config) 方法,返回的也是一個promise ,發請求的具體方法 就在這個函數內部,源碼就不貼了 (有興趣的小夥伴可以看一下)。
現在我總結一下這個流程 request(config) —> dispathRequest(config) —> xhrAdapter(config)
request(config) :|將請求攔截器/ dispatchRequest() /響應攔截器通過promise鏈串連起來,返回promise
dispatchRequest(config):轉換請求數據 —> 調用xhrAdapter()發請求 —>請求返回後轉換響應數據。返回promise
xhrAdapter(config):創建XHR對象,根據config進行相應設置,發送特定請求,並接收響應數據,返回promise
文章最後有這個流程圖
axios取消請求cancel
lib下有一個cancel目錄,裏面有三個文件分別爲 Cancel.js,CancelToken.js,isCancel.js
Cancel模塊
//構造函數 也是一個error
function Cancel(message) {
this.message = message;
}
Cancel.prototype.toString = function toString() {
return 'Cancel' + (this.message ? ': ' + this.message : '');
};
Cancel.prototype.__CANCEL__ = true;
module.exports = Cancel;
isCancel模塊
//判斷 是否是這個Cancel 還是一個一般的error
module.exports = function isCancel(value) {
return !!(value && value.__CANCEL__);
};
CancelToken模塊
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;
}
//Cancel模塊
token.reason = new Cancel(message);
//將取消請求的promise 指定爲成功 值爲reason
resolvePromise(token.reason);
//之後就會執行xhr.js模塊的中的config.cancelToken.promise.then()
});
xhr.js
//xhr中的 有關中斷請求的部分代碼
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
// 中斷
request.abort();
//promise進入失敗 傳入這個cancel對象 這個就是 error
reject(cancel);
// Clean up request
request = null;
});
整體流程圖
爲什麼把流程圖放最後而不是放前面,因爲這個圖 不瞭解源碼看了跟沒看一樣。