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
取消的流程,看下源码如下所示:
'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;
'use strict';
/*
用于判断一个error是不是一个cancel错误
*/
module.exports = function isCancel(value) {
return !!(value && value.__CANCEL__);
};
'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;
如何取消未完成的请求,如下所示:
cancelToken
对象时,保存 cancel
函数,如下:
cancelPromise
cancel
函数cancel
函数传递出来cancel()
取消请求,如下:
cacel
函数, 传入错误信息 message
cancelPromise
变为成功, 且成功的值为一个 Cancel
对象cancelPromise
的成功回调中中断请求, 并让发请求的 proimse
失败, 失败的 reason
为 Cacel
对象