Axios 是一個基於 promise 的 HTTP 庫,能夠用在瀏覽器和 node.js 中。html
但願經過源碼來慢慢理清這些功能的實現原理node
執行 GET
請求ios
axios.get('/user?ID=12345')
.then(function (response) {
console.log(response);
})
複製代碼
複製代碼
執行 POST
請求git
axios.post('/user', {
name: 'zxm',
age: 18,
})
.then(function (response) {
console.log(response);
})
複製代碼
複製代碼
使用方式不是本次主題的重點,具體使用方式能夠參照 Axios 中文說明github
源碼拉下來直接進入 lib 文件夾開始解讀源碼ajax
lib/ axios.js 開始json
'use strict';
var utils = require('./utils');
var bind = require('./helpers/bind');
var Axios = require('./core/Axios');
var mergeConfig = require('./core/mergeConfig');
var defaults = require('./defaults');
// 重點 createInstance 方法
// 先眼熟一個代碼 下面講完工具函數會再具體來說解 createInstance
function createInstance(defaultConfig) {
// 實例化 Axios
var context = new Axios(defaultConfig);
// 自定義 bind 方法 返回一個函數()=> {Axios.prototype.request.apply(context,args)}
var instance = bind(Axios.prototype.request, context);
// Axios 源碼的工具類
utils.extend(instance, Axios.prototype, context);
utils.extend(instance, context);
return instance;
}
// 傳入一個默認配置 defaults 配置先無論,後面會有具體的細節
var axios = createInstance(defaults);
// 下面都是爲 axios 實例化的對象增長不一樣的方法。
axios.Axios = Axios;
axios.create = function create(instanceConfig) {
return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
axios.all = function all(promises) {
return Promise.all(promises);
};
axios.spread = require('./helpers/spread');
module.exports = axios;
module.exports.default = axios;
複製代碼
複製代碼
lib/ util.js 工具方法axios
有以下方法:api
module.exports = {
isArray: isArray,
isArrayBuffer: isArrayBuffer,
isBuffer: isBuffer,
isFormData: isFormData,
isArrayBufferView: isArrayBufferView,
isString: isString,
isNumber: isNumber,
isObject: isObject,
isUndefined: isUndefined,
isDate: isDate,
isFile: isFile,
isBlob: isBlob,
isFunction: isFunction,
isStream: isStream,
isURLSearchParams: isURLSearchParams,
isStandardBrowserEnv: isStandardBrowserEnv,
forEach: forEach,
merge: merge,
deepMerge: deepMerge,
extend: extend,
trim: trim
};
複製代碼
複製代碼
is開頭的isXxx方法名 都是判斷是不是 Xxx
類型 ,這裏就不作明說 主要是看下 後面幾個方法數組
extend 將 b 裏面的屬性和方法繼承給 a , 而且將 b 裏面的方法的執行上個下文都綁定到 thisArg
// a, b,thisArg 參數都爲一個對象
function extend(a, b, thisArg) {
forEach(b, function assignValue(val, key) {
// 若是指定了 thisArg 那麼綁定執行上下文到 thisArg
if (thisArg && typeof val === 'function') {
a[key] = bind(val, thisArg);
} else {
a[key] = val;
}
});
return a;
}
複製代碼
複製代碼
抽象的話看個例子
這樣是否是就一目瞭然。fn2 函數沒有拿本身對象內的 age = 20 而是被指定到了 thisArg 中的 age
自定義 forEach 方法遍歷基本數據,數組,對象。
function forEach(obj, fn) {
if (obj === null || typeof obj === 'undefined') {
return;
}
if (typeof obj !== 'object') {
obj = [obj];
}
if (isArray(obj)) {
for (var i = 0, l = obj.length; i < l; i++) {
fn.call(null, obj[i], i, obj);
}
} else {
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
fn.call(null, obj[key], key, obj);
}
}
}
}
複製代碼
複製代碼
merge 合併對象的屬性,相同屬性後面的替換前的
function merge(/* obj1, obj2, obj3, ... */) {
var result = {};
function assignValue(val, key) {
if (typeof result[key] === 'object' && typeof val === 'object') {
result[key] = merge(result[key], val);
} else {
result[key] = val;
}
}
for (var i = 0, l = arguments.length; i < l; i++) {
forEach(arguments[i], assignValue);
}
return result;
}
複製代碼
複製代碼
以下圖所示:
bind -> lib/ helpers/ bind.js 這個很清楚,返回一個函數,而且傳入的方法執行上下文綁定到 thisArg上。
module.exports = function bind(fn, thisArg) {
return function wrap() {
var args = new Array(arguments.length);
for (var i = 0; i < args.length; i++) {
args[i] = arguments[i];
}
return fn.apply(thisArg, args);
};
};
複製代碼
複製代碼
好勒那麼 axios/util 方法咱們就基本沒有問題拉
看完這些工具類方法後咱們在回過頭看以前的 createInstance 方法
function createInstance(defaultConfig) {
// 實例化 Axios, Axios下面會講到
var context = new Axios(defaultConfig);
// 將 Axios.prototype.request 的執行上下文綁定到 context
// bind 方法返回的是一個函數
var instance = bind(Axios.prototype.request, context);
// 將 Axios.prototype 上的全部方法的執行上下文綁定到 context , 而且繼承給 instance
utils.extend(instance, Axios.prototype, context);
// 將 context 繼承給 instance
utils.extend(instance, context);
return instance;
}
// 傳入一個默認配置
var axios = createInstance(defaults);
複製代碼
複製代碼
總結:createInstance
函數返回了一個函數 instance.
'use strict';
var utils = require('./../utils');
var buildURL = require('../helpers/buildURL');
var InterceptorManager = require('./InterceptorManager');
var dispatchRequest = require('./dispatchRequest');
var mergeConfig = require('./mergeConfig');
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
// 核心方法 request
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 上面增長 delete,get,head,options 方法
// 這樣咱們就可使用 axios.get(), axios.post() 等等方法
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
Axios.prototype[method] = function(url, config) {
// 都是調用了 this.request 方法
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
}));
};
});
module.exports = Axios;
複製代碼
複製代碼
上面的全部的方法都是經過調用了 this.request 方法
那麼咱們就來看這個 request 方法,我的認爲是源碼內的精華也是比較難理解的部分,使用到了 Promise 的鏈式調用,也使用到了中間件的思想。
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
Axios.prototype.request = function request(config) {
// 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);
// 請求方式,沒有默認爲 get
config.method = config.method ? config.method.toLowerCase() : 'get';
// 重點 這個就是攔截器的中間件
var chain = [dispatchRequest, undefined];
// 生成一個 promise 對象
var promise = Promise.resolve(config);
// 將請求前方法置入 chain 數組的前面 一次置入兩個 成功的,失敗的
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// 將請求後的方法置入 chain 數組的後面 一次置入兩個 成功的,失敗的
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
// 經過 shift 方法把第一個元素從其中刪除,並返回第一個元素。
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
複製代碼
複製代碼
看到這裏有點抽象,不要緊。咱們先講下攔截器。在請求或響應被 then
或 catch
處理前攔截它們。使用方法參考 Axios 中文說明 ,大體使用以下。
// 添加請求攔截器
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);
});
複製代碼
複製代碼
經過 promise 鏈式調用一個一個函數,這個函數就是 chain 數組裏面的方法
// 初始的 chain 數組 dispatchRequest 是發送請求的方法
var chain = [dispatchRequest, undefined];
// 而後 遍歷 interceptors
// 注意 這裏的 forEach 不是 Array.forEach, 也不是上面講到的 util.forEach. 具體 攔截器源碼 會講到
// 如今咱們只要理解他是遍歷給 chain 裏面追加兩個方法就能夠
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 會是什麼樣子呢 (重點)
chain = [ 請求攔截器的成功方法,請求攔截器的失敗方法,dispatchRequest, undefined, 響應攔截器的成功方法,響應攔截器的失敗方法 ]。
複製代碼
複製代碼
好了,知道具體使用使用以後是什麼樣子呢?回過頭去看 request 方法
每次請求的時候咱們有一個
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
意思就是將 chainn 內的方法兩兩拿出來執行 成以下這樣
promise.then(請求攔截器的成功方法, 請求攔截器的失敗方法)
.then(dispatchRequest, undefined)
.then(響應攔截器的成功方法, 響應攔截器的失敗方法)
複製代碼
複製代碼
如今看是否是清楚了不少,攔截器的原理。如今咱們再來看 InterceptorManager 的源碼
lib/ core/ InterceptorManager.js
'use strict';
var utils = require('./../utils');
function InterceptorManager() {
// 存放方法的數組
this.handlers = [];
}
// 經過 use 方法來添加攔截方法
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};
// 經過 eject 方法來刪除攔截方法
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
};
// 添加一個 forEach 方法,這就是上述說的 forEach
InterceptorManager.prototype.forEach = function forEach(fn) {
// 裏面仍是依舊使用了 utils 的 forEach, 不要糾結這些 forEach 的具體代碼
// 明白他們幹了什麼就能夠
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
};
module.exports = InterceptorManager;
複製代碼
複製代碼
lib/ core/ dispatchRequest .js
'use strict';
var utils = require('./../utils');
var transformData = require('./transformData');
var isCancel = require('../cancel/isCancel');
var defaults = require('../defaults');
var isAbsoluteURL = require('./../helpers/isAbsoluteURL');
var combineURLs = require('./../helpers/combineURLs');
// 請求取消時候的方法,暫不看
function throwIfCancellationRequested(config) {
if (config.cancelToken) {
config.cancelToken.throwIfRequested();
}
}
module.exports = function dispatchRequest(config) {
throwIfCancellationRequested(config);
// 請求沒有取消 執行下面的請求
if (config.baseURL && !isAbsoluteURL(config.url)) {
config.url = combineURLs(config.baseURL, config.url);
}
config.headers = config.headers || {};
// 轉換數據
config.data = transformData(
config.data,
config.headers,
config.transformRequest
);
// 合併配置
config.headers = utils.merge(
config.headers.common || {},
config.headers[config.method] || {},
config.headers || {}
);
utils.forEach(
['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
function cleanHeaderConfig(method) {
delete config.headers[method];
}
);
// 這裏是重點, 獲取請求的方式,下面會將到
var adapter = config.adapter || defaults.adapter;
return adapter(config).then(function onAdapterResolution(response) {
throwIfCancellationRequested(config);
// 難道了請求的數據, 轉換 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);
});
};
複製代碼
複製代碼
看了這麼多,咱們還沒看到是經過什麼來發送請求的,如今咱們看看在最開始實例化 createInstance 方法中咱們傳入的 defaults
是什麼
var axios = createInstance(defaults);
lib/ defaults.js
'use strict';
var utils = require('./utils');
var normalizeHeaderName = require('./helpers/normalizeHeaderName');
var DEFAULT_CONTENT_TYPE = {
'Content-Type': 'application/x-www-form-urlencoded'
};
function setContentTypeIfUnset(headers, value) {
if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) {
headers['Content-Type'] = value;
}
}
// getDefaultAdapter 方法是來獲取請求的方式
function getDefaultAdapter() {
var adapter;
// process 是 node 環境的全局變量
if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
// 若是是 node 環境那麼久經過 node http 的請求方法
adapter = require('./adapters/http');
} else if (typeof XMLHttpRequest !== 'undefined') {
// 若是是瀏覽器啥的 有 XMLHttpRequest 的就用 XMLHttpRequest
adapter = require('./adapters/xhr');
}
return adapter;
}
var defaults = {
// adapter 就是請求的方法
adapter: getDefaultAdapter(),
// 下面一些請求頭,轉換數據,請求,詳情的數據
// 這也就是爲何咱們能夠直接拿到請求的數據時一個對象,若是用 ajax 咱們拿到的都是 jSON 格式的字符串
// 而後每次都經過 JSON.stringify(data)來處理結果。
transformRequest: [function transformRequest(data, headers) {
normalizeHeaderName(headers, 'Accept');
normalizeHeaderName(headers, 'Content-Type');
if (utils.isFormData(data) ||
utils.isArrayBuffer(data) ||
utils.isBuffer(data) ||
utils.isStream(data) ||
utils.isFile(data) ||
utils.isBlob(data)
) {
return data;
}
if (utils.isArrayBufferView(data)) {
return data.buffer;
}
if (utils.isURLSearchParams(data)) {
setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
return data.toString();
}
if (utils.isObject(data)) {
setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
return JSON.stringify(data);
}
return data;
}],
transformResponse: [function transformResponse(data) {
/*eslint no-param-reassign:0*/
if (typeof data === 'string') {
try {
data = JSON.parse(data);
} catch (e) { /* Ignore */ }
}
return data;
}],
/**
* A timeout in milliseconds to abort a request. If set to 0 (default) a
* timeout is not created.
*/
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
validateStatus: function validateStatus(status) {
return status >= 200 && status < 300;
}
};
defaults.headers = {
common: {
'Accept': 'application/json, text/plain, */*'
}
};
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);
});
module.exports = defaults;
複製代碼
複製代碼
若是看的還不是很明白,不用擔憂,這基本上是我表達,書寫的不夠好。由於在寫篇文章時我也曾反覆的刪除,重寫,總以爲表達的不夠清楚。爲了增強理解和學習你們能夠去 github 將代碼拉下來對照着來看。
git clone https://github.com/axios/axios.git
全文章,若有錯誤或不嚴謹的地方,請務必給予指正,謝謝!
參考: