一、源碼實現分析
-
axios
源碼目錄結構,如下所示:├── /dist/ # 項目輸出目錄 ├── /lib/ # 項目源碼目錄 │ ├── /adapters/ # 定義請求的適配器 xhr、http │ │ ├── http.js # 實現http適配器(包裝http包) │ │ └── xhr.js # 實現xhr適配器(包裝xhr對象) │ ├── /cancel/ # 定義取消功能 │ ├── /core/ # 一些核心功能 │ │ ├── Axios.js # axios的核心主類 │ │ ├── dispatchRequest.js # 用來調用http請求適配器方法發送請求的函數 │ │ ├── InterceptorManager.js # 攔截器的管理器 │ │ └── settle.js # 根據http響應狀態,改變Promise的狀態 │ ├── /helpers/ # 一些輔助方法 │ ├── axios.js # 對外暴露接口 │ ├── defaults.js # axios的默認配置 │ └── utils.js # 公用工具 ├── package.json # 項目信息 ├── index.d.ts # 配置TypeScript的聲明文件 └── index.js # 入口文件
-
axios
與Axios
的關係,如下所示:
- 從語法上來說:
axios
不是Axios
的實例 - 從功能上來說:
axios
是Axios
的實例 axios
函數對應的是Axios.prototype.request
方法通過bind(Axiox的實例)
產生的函數axios
作爲對象有Axios
原型對象上的所有方法, 有Axios
對象上所有屬性axios
有Axios
原型上的所有發特定類型請求的方法:get()/post()/put()/delete()
axios
有Axios
的實例上的所有屬性:defaults/interceptors
,後面又添加了create()/CancelToken()/all()
instance
與axios
的區別? 如下所示:
- 相同點:
- 都是一個能發任意請求的函數:
request(config)
- 都有發特定請求的各種方法:
get()/post()/put()/delete()
- 都有默認配置和攔截器的屬性:
defaults/interceptors
- 都是一個能發任意請求的函數:
- 不同點:
- 默認匹配的值很可能不一樣
instance
沒有axios
後面添加的一些方法:create()/CancelToken()/all()
- 看一下
axios
的核心源碼,代碼如下所示:
'use strict';
var utils = require('./../utils');
var buildURL = require('../helpers/buildURL');
var InterceptorManager = require('./InterceptorManager');
var dispatchRequest = require('./dispatchRequest');
var mergeConfig = require('./mergeConfig');
/**
* Axios構造函數
* Create a new instance of Axios
* @param {Object} instanceConfig The default config for the instance
*/
function Axios(instanceConfig) {
// 將指定的config, 保存爲defaults屬性
this.defaults = instanceConfig;
// 將包含請求/響應攔截器管理器的對象保存爲interceptors屬性
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
/**
* 用於發請求的函數
* 我們使用的axios就是此函數bind()返回的函數
*
* Dispatch a request
*
* @param {Object} config The config specific for this request (merged with this.defaults)
*/
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);
// 添加method配置, 默認爲get
config.method = config.method ? config.method.toLowerCase() : 'get';
/*
創建用於保存請求/響應攔截函數的數組
數組的中間放發送請求的函數
數組的左邊放請求攔截器函數(成功/失敗)
數組的右邊放響應攔截器函數
*/
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);
});
// 通過promise的then()串連起所有的請求攔截器/請求方法/響應攔截器
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
// 返回用來指定我們的onResolved和onRejected的promise
return promise;
};
// 用來得到帶query參數的url
Axios.prototype.getUri = function getUri(config) {
config = mergeConfig(this.defaults, config);
return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, '');
};
// Provide aliases for supported request methods
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
}));
};
});
module.exports = Axios;
axios
運行的整體流程,如下所示:
- 整體流程:
request(config) ===> dispatchRequest(config) ===> xhrAdapter(config)
request(config)
: 將請求攔截器 /dispatchRequest()
/ 響應攔截器 通過promise
鏈串連起來, 返回promise
dispatchRequest(config)
: 轉換請求數據 ===> 調用xhrAdapter()
發請求 ===> 請求返回後轉換響應數據. 返回promise
xhrAdapter(config)
: 創建XHR
對象, 根據config
進行相應設置, 發送特定請求, 並接收響應數據, 返回promise
-
對於
axios
的流程,也可以這麼理解:axios
和axios.create()
一起發送請求,被createInstance()
一起接收,配合config
執行/別名執行,Axios.prototype.request
執行,然後執行request.interceptors
,配合處理參數與默認參數/transformdata
執行dispatchRequest
,然後執行adapter
。如果報錯或者是cancel
取消,那麼執行axios.rejected
。如果正確,那麼執行axios fulfilled
。response interceptors
執行,最後請求的onResolved
或者是onRejected
。 -
對於
request
的流程,如下所示:requestInterceptors: [{fulfilled1(){}, rejected1(){}}, {fulfilled2(){}, rejected2(){}}] responseInterceptors: [{fulfilled11(){}, rejected11(){}}, {fulfilled22(){}, rejected22(){}}] chain: [ fulfilled2, rejected2, fulfilled1, rejected1, dispatchReqeust, undefined, fulfilled11, rejected11, fulfilled22, rejected22 ] promise鏈回調: config => (fulfilled2, rejected2) => (fulfilled1, rejected1) // 請求攔截器處理 => (dispatchReqeust, undefined) // 發請求 => (fulfilled11, rejected11) => (fulfilled22, rejected22) // 響應攔截器處理 => (onResolved, onRejected) // axios發請求回調處理
-
對於
dispatchRequest
的流程,看下源碼如下所示:
-
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'); /** * Throws a `Cancel` if cancellation has been requested. */ function throwIfCancellationRequested(config) { if (config.cancelToken) { config.cancelToken.throwIfRequested(); } } /** * Dispatch a request to the server using the configured adapter. * * @param {object} config The config that is to be used for the request * @returns {Promise} The Promise to be fulfilled */ module.exports = function dispatchRequest(config) { /* 如果請求已經被取消, 直接拋出異常 */ throwIfCancellationRequested(config); /* 合併config中的baseURL和url */ if (config.baseURL && !isAbsoluteURL(config.url)) { config.url = combineURLs(config.baseURL, config.url); } // Ensure headers exist config.headers = config.headers || {}; /* 對config中的data進行必要的轉換處理 設置相應的Content-Type請求頭 */ config.data = transformData( config.data, config.headers, config.transformRequest ); /* 整合config中所有的header */ 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); /* 對response中還沒有解析的data數據進行解析 json字符串解析爲js對象/數組 */ 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); }); }; ``` - **InterceptorManager.js** : ```js 'use strict'; var utils = require('./../utils'); function InterceptorManager() { // 用來保存攔截器函數的數組, 數組中每個都是對象, 對象中包含fulfilled/rejected方法 this.handlers = []; } /** * Add a new interceptor to the stack * * @param {Function} fulfilled The function to handle `then` for a `Promise` * @param {Function} rejected The function to handle `reject` for a `Promise` * * @return {Number} An ID used to remove interceptor later */ InterceptorManager.prototype.use = function use(fulfilled, rejected) { // 添加成功和失敗的攔截器函數 this.handlers.push({ fulfilled: fulfilled, rejected: rejected }); // 返回攔截器對應的ID(也就是下標) return this.handlers.length - 1; }; /** * Remove an interceptor from the stack * * @param {Number} id The ID that was returned by `use` */ InterceptorManager.prototype.eject = function eject(id) { // 移除指定id對應的攔截器 if (this.handlers[id]) { this.handlers[id] = null; } }; /** * Iterate over all the registered interceptors * * This method is particularly useful for skipping over any * interceptors that may have become `null` calling `eject`. * * @param {Function} fn The function to call for each interceptor */ InterceptorManager.prototype.forEach = function forEach(fn) { // 遍歷處理所有保存的攔截器 utils.forEach(this.handlers, function forEachHandler(h) { if (h !== null) { fn(h); } }); }; module.exports = InterceptorManager; ``` - **defaults.js** : ```js 'use strict'; var utils = require('./utils'); var normalizeHeaderName = require('./helpers/normalizeHeaderName'); // 默認的Content-Type頭的值 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; } } function getDefaultAdapter() { var adapter; // Only Node.JS has a process variable that is of [[Class]] process if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') { // For node use HTTP adapter adapter = require('./adapters/http'); } else if (typeof XMLHttpRequest !== 'undefined') { // For browsers use XHR adapter adapter = require('./adapters/xhr'); } return adapter; } var defaults = { // 得到當前環境對應的請求適配器 adapter: getDefaultAdapter(), // 請求轉換器 transformRequest: [function transformRequest(data, headers) { // 指定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(); } // 如果data是對象, 指定請求體參數格式爲json, 並將參數數據對象轉換爲json if (utils.isObject(data)) { setContentTypeIfUnset(headers, 'application/json;charset=utf-8'); return JSON.stringify(data); } return data; }], // 響應數據轉換器: 解析字符串類型的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, // 判斷響應狀態碼的合法性: [200, 299] validateStatus: function validateStatus(status) { return status >= 200 && status < 300; } }; defaults.headers = { // 包含所有通用的請求的對象 common: { 'Accept': 'application/json, text/plain, */*' } }; // 指定delete/get/head請求方式的請求頭容器對象 utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) { defaults.headers[method] = {}; }); // 指定post/put/patch請求方式的請求頭容器對象 utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { // 指定了默認的Content-Type頭 defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE); }); module.exports = defaults; ```
axios
的請求/響應攔截器是什麼? 如下所示:
- 請求攔截器: 在真正發請求前,可以對請求進行檢查或配置進行特定處理的函數, 包括成功/失敗的函數, 傳遞的必須是
config
。失敗的回調函數, 傳遞的默認是error
。 - 響應攔截器: 在請求返回後,可以對響應數據進行特定處理的函數,包括成功/失敗的函數,傳遞的默認是
response
。失敗的回調函數, 傳遞的默認是error
。
axios
的請求/響應數據轉換器是什麼? 如下所示:
-
請求轉換器:對請求頭和請求體數據進行特定處理的函數
setContentTypeIfUnset(headers, 'application/json;charset=utf-8'); return JSON.stringify(data)
,如下:if (utils.isObject(data)) { setContentTypeIfUnset(headers, 'application/json;charset=utf-8'); return JSON.stringify(data); }
-
響應轉換器: 將響應體
json
字符串解析爲js
對象或數組的函數
response.data = JSON.parse(response.data)
- 對於
xhrAdapter
的流程,看下 xhr.js 的源碼,如下所示:
'use strict';
var utils = require('./../utils');
var settle = require('./../core/settle');
var buildURL = require('./../helpers/buildURL');
var parseHeaders = require('./../helpers/parseHeaders');
var isURLSameOrigin = require('./../helpers/isURLSameOrigin');
var createError = require('../core/createError');
module.exports = function xhrAdapter(config) {
// 返回一個promise
return new Promise(function dispatchXhrRequest(resolve, reject) {
var requestData = config.data;
var requestHeaders = config.headers;
if (utils.isFormData(requestData)) {
delete requestHeaders['Content-Type']; // Let the browser set it
}
// 創建XHR對象
var request = new XMLHttpRequest();
// HTTP basic authentication
if (config.auth) {
var username = config.auth.username || '';
var password = config.auth.password || '';
requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
}
// 初始化請求
request.open(config.method.toUpperCase(), buildURL(config.url, config.params, config.paramsSerializer), true);
// 指定超時時間(單位ms)
request.timeout = config.timeout;
// 綁定請求狀態改變的監聽
request.onreadystatechange = function handleLoad() {
// request不存在或請求狀態不是4, 直接結束
if (!request || request.readyState !== 4) {
return;
}
// The request errored out and we didn't get a response, this will be
// handled by onerror instead
// With one exception: request that using file: protocol, most browsers
// will return status as 0 even though it's a successful request
if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
return;
}
// 準備response對象
var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;
var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response;
var response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config: config,
request: request
};
// 根據響應狀態碼來確定請求的promise的結果狀態(成功/失敗)
settle(resolve, reject, response);
// 將請求對象賦空
request = null;
};
// 綁定請求中斷監聽
request.onabort = function handleAbort() {
if (!request) {
return;
}
// reject promise, 指定aborted的error
reject(createError('Request aborted', config, 'ECONNABORTED', request));
// Clean up request
request = null;
};
// Handle low level network errors
request.onerror = function handleError() {
// Real errors are hidden from us by the browser
// onerror should only fire if it's a network error
reject(createError('Network Error', config, null, request));
// Clean up request
request = null;
};
// Handle timeout
request.ontimeout = function handleTimeout() {
reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED',
request));
// Clean up request
request = null;
};
// Add xsrf header
// This is only done if running in a standard browser environment.
// Specifically not if we're in a web worker, or react-native.
if (utils.isStandardBrowserEnv()) {
var cookies = require('./../helpers/cookies');
// Add xsrf header
var xsrfValue = (config.withCredentials || isURLSameOrigin(config.url)) && config.xsrfCookieName ?
cookies.read(config.xsrfCookieName) :
undefined;
if (xsrfValue) {
requestHeaders[config.xsrfHeaderName] = xsrfValue;
}
}
// 如果沒有指定請求體參數, 刪除Content-Type請求頭, 其它所有請求頭都設置到request上
// Add headers to the request
if ('setRequestHeader' in request) {
utils.forEach(requestHeaders, function setRequestHeader(val, key) {
if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') {
// Remove Content-Type if data is undefined
delete requestHeaders[key];
} else {
// Otherwise add header to the request
request.setRequestHeader(key, val);
}
});
}
// Add withCredentials to request if needed
if (config.withCredentials) {
request.withCredentials = true;
}
// 如果需要指定responseType
// Add responseType to request if needed
if (config.responseType) {
try {
request.responseType = config.responseType;
} catch (e) {
// Expected DOMException thrown by browsers not compatible XMLHttpRequest Level 2.
// But, this can be suppressed for 'json' type as it can be parsed by default 'transformResponse' function.
if (config.responseType !== 'json') {
throw e;
}
}
}
// 綁定下載進度的監聽
// Handle progress if needed
if (typeof config.onDownloadProgress === 'function') {
request.addEventListener('progress', config.onDownloadProgress);
}
// 綁定上傳進度的監聽
// Not all browsers support upload events
if (typeof config.onUploadProgress === 'function' && request.upload) {
request.upload.addEventListener('progress', config.onUploadProgress);
}
// 如果配置了cancelToken
if (config.cancelToken) {
// 指定用於中斷請求的回調函數
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
// 中斷請求
request.abort();
// 讓請求的promise失敗
reject(cancel);
// Clean up request
request = null;
});
}
if (requestData === undefined) {
requestData = null;
}
// 發送請求, 指定請求體數據, 可能是null
request.send(requestData);
});
};
-
response
的整體結構,如下所示:{ data, status, statusText, headers, config, request }
-
error
的整體結構,如下所示:{ message, request, response }
-
對於
cancel
取消的流程,看下源碼如下所示:- Cancel.js :
'use strict'; /** * 當取消一個請求時, 需要將Cancel對象作爲一個error拋出 * * A `Cancel` is an object that is thrown when an operation is canceled. * * @class * @param {string=} message The message. */ function Cancel(message) { this.message = message; } Cancel.prototype.toString = function toString() { return 'Cancel' + (this.message ? ': ' + this.message : ''); }; // 用於標識是一個取消的error Cancel.prototype.__CANCEL__ = true; module.exports = Cancel;
- isCancel.js :
'use strict'; /* 用於判斷一個error是不是一個cancel錯誤 */ module.exports = function isCancel(value) { return !!(value && value.__CANCEL__); };
- CancelToken.js :
'use strict'; var Cancel = require('./Cancel'); /** * 用於取消請求的對象構造函數 * * A `CancelToken` is an object that can be used to request cancellation of an operation. * * @class * @param {Function} executor The executor function. */ function CancelToken(executor) { if (typeof executor !== 'function') { throw new TypeError('executor must be a function.'); } // 爲取消請求準備一個promise對象, 並保存resolve函數 var resolvePromise; this.promise = new Promise(function promiseExecutor(resolve) { resolvePromise = resolve; }); // 保存當前token對象 var token = this; // 立即執行接收的執行器函數, 並傳入用於取消請求的cancel函數 executor(function cancel(message) { // 如果token中有reason了, 說明請求已取消 if (token.reason) { // Cancellation has already been requested return; } // 將token的reason指定爲一個Cancel對象 token.reason = new Cancel(message); // 將取消請求的promise指定爲成功, 值爲reason resolvePromise(token.reason); }); } /** * 如果請求已經被取消, 拋出reason也就是Cancel對象的異常 * Throws a `Cancel` if cancellation has been requested. */ CancelToken.prototype.throwIfRequested = function throwIfRequested() { if (this.reason) { throw this.reason; } }; /** * 創建一個包含token對象和cancel函數的對象, 並添加給CancelToken * * Returns an object that contains a new `CancelToken` and a function that, when called, * cancels the `CancelToken`. */ CancelToken.source = function source() { var cancel; var token = new CancelToken(function executor(c) { cancel = c; }); return { token: token, cancel: cancel }; }; module.exports = CancelToken;
- Cancel.js :
-
如何取消未完成的請求,如下所示:
- 當配置了
cancelToken
對象時,保存cancel
函數,如下:- 創建一個用於將來中斷請求的
cancelPromise
- 並定義了一個用於取消請求的
cancel
函數 - 將
cancel
函數傳遞出來
- 創建一個用於將來中斷請求的
- 調用
cancel()
取消請求,如下:- 執行
cacel
函數, 傳入錯誤信息message
- 內部會讓
cancelPromise
變爲成功, 且成功的值爲一個Cancel
對象 - 在
cancelPromise
的成功回調中中斷請求, 並讓發請求的proimse
失敗, 失敗的reason
爲Cacel
對象
- 執行