Axios构造函数
Axios构造函数实例化出的axios对象拥有拦截器属性,拦截器属性里有两个拦截器对象,一个是请求拦截器,一个是响应拦截器
下面是结构图
然后就是整个Axios最核心的东西,Axios.prototype.request方法,使用axios发请求就是在调用这个方法。
Axios.prototype.request会将请求拦截器,请求,响应拦截器放在一个数组里,形成一个队列,然后按照顺序用Promise.ptotorype.then方法调用,then方法的返回值还是一个promise,因此就形成一个then链,直到队列中所有操作都执行结束,然后将最终的promise返回。这就是Axios的核心实现。
下面是then链的流程图:
下面是Axios.js源代码:
'use strict';
var utils = require('./../utils');
var buildURL = require('../helpers/buildURL');
var InterceptorManager = require('./InterceptorManager');
var dispatchRequest = require('./dispatchRequest');
var mergeConfig = require('./mergeConfig');
/**
* Create a new instance of Axios
*
* @param {Object} instanceConfig The default config for the instance
*/
function Axios(instanceConfig) {
this.defaults = instanceConfig;//axios实例上的defaults属性,存放默认设置
this.interceptors = {//axios上的拦截器属性,是一个对象,里面分别存放请求和相应的拦截器对象
//InterceptorManager对象在添加了拦截器之后,里面的一个拦截器其实是存了拥有两个属性的对象,fulfilled和rejected,分别是promise成功和失败时的回调函数
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
/**
* 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是字符串,说明是请求的url参数
config = arguments[1] || {};//第二个参数是请求的config
config.url = arguments[0];//第一个参数赋值给config的url属性
} else {
config = config || {};//否则config就是请求配置
}
config = mergeConfig(this.defaults, config);
//调用mergeConfig将默认设置和自定义设置合并,this.defaults上同名属性会覆盖config上的
config.method = config.method ? config.method.toLowerCase() : 'get';
//method配置转换成小写字母,如果没传递,默认是get
// Hook up interceptors middleware
var chain = [dispatchRequest, undefined];
//请求和拦截器的执行链
//执行链数组中的元素都是函数,两个为一组,下面的操作会将两个为一组传递给promise.then()
//Promise.prototype.then接收两个参数,第一个是resolve,第二个是reject
//then方法返回的是一个新的Promise实例,因此可以使用链式写法
var promise = Promise.resolve(config);//初始promise
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
//遍历this.interceptors.request.handlers数组,对其中的每一个拦截器执行unshiftRequestInterceptors函数
chain.unshift(interceptor.fulfilled, interceptor.rejected);
//将请求拦截器插入到执行链的最开头位置
});
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
//遍历this.interceptors.response.handlers数组,对其中的每一个拦截器执行pushResponseInterceptors函数
chain.push(interceptor.fulfilled, interceptor.rejected);
//将响应拦截器插入到执行链的结尾位置
});
while (chain.length) {
//循环执行链数组,将两个为一组传递给promise.then()
//将执行链里的请求拦截器和dispatchRequest和响应拦截器按顺序执行完
promise = promise.then(chain.shift(), chain.shift());
}
return promise;//返回链式promise最终返回的promise对象
};
Axios.prototype.getUri = function getUri(config) {//获取带有查询字符串的url
config = mergeConfig(this.defaults, config);
//调用mergeConfig将默认设置和自定义设置合并,this.defaults上同名属性会覆盖config上的
return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, '');
//返回带有查询字符串的url,将斜杠转换为空字符串
};
// Provide aliases for supported request methods
//为以下类型请求提供别名调用方法,例如Axios.prototype.delete
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;
下面是InterceptorManager.js源代码:
'use strict';
var utils = require('./../utils');
function InterceptorManager() {//InterceptorManager构造函数
this.handlers = [];//存拦截器的数组属性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
*/
//原型上的use方法,用于添加新的拦截器到InterceptorManager对象的handlers属性里
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
//fulfilled是promise的成功处理函数,rejected是promise的失败处理函数
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;//返回当前拦截器在handlers中的索引,便于之后移除
};
/**
* Remove an interceptor from the stack
*
* @param {Number} id The ID that was returned by `use`
*/
//从handlers中删除拦截器,参数id是use方法返回的handlers数组索引
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {//如果handlers数组中存在id对应的拦截器,就赋值为null
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
*/
//遍历所有已注册的拦截器
//这个方法在想要跳过被移除的拦截器的时候非常有用,因为调用eject后对应的拦截器被赋值为null
//参数fn会在遍历的时候调用
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {//如果拦截器不为null,传递给fn作为参数来调用
fn(h);
}
});
};
module.exports = InterceptorManager;
buildURL方法,创建一个带有查询字符串参数的url
'use strict';
var utils = require('./../utils');
function encode(val) {//encodeURIComponent转码后将一些特殊字符变回原样
return encodeURIComponent(val).
replace(/%40/gi, '@').
replace(/%3A/gi, ':').
replace(/%24/g, '$').
replace(/%2C/gi, ',').
replace(/%20/g, '+').
replace(/%5B/gi, '[').
replace(/%5D/gi, ']');
}
/**
* Build a URL by appending params to the end
*
* @param {string} url The base of the url (e.g., http://)
* @param {object} [params] The params to be appended
* @returns {string} The formatted url
*/
//创建一个url,将params参数也就是get请求参数连在原url的后面
module.exports = function buildURL(url, params, paramsSerializer) {
/*eslint no-param-reassign:0*/
if (!params) {//如果没有params参数,直接返回原url
return url;
}
var serializedParams;
if (paramsSerializer) {
//如果传递了paramsSerializer,就调用将params序列化,结果存入变量serializedParams
//paramsSerializer是用户传递的自定义函数
serializedParams = paramsSerializer(params);
} else if (utils.isURLSearchParams(params)) {
//如果没有传递paramsSerializer,就判断params是否是URLSearchParams对象
//如果是,就调用URLSearchParams.toString(),将其变成字符串可以直接使用
serializedParams = params.toString();
} else {//既没有传递paramsSerializer也不是原生URLSearchParams对象,执行下面操作
var parts = [];//key=value形式的字符串
utils.forEach(params, function serialize(val, key) {
//调用forEach循环params对象
if (val === null || typeof val === 'undefined') {
//如果当前params的值是空,就不做操作
return;
}
if (utils.isArray(val)) {//判断当前params的值是否是一个数组
key = key + '[]';//如果是数组,key后面连上数组索引的方括号
} else {
val = [val];//如果不是数组,当前值等于只有val一个元素的数组
}
utils.forEach(val, function parseValue(v) {
//循环val数组
if (utils.isDate(v)) {//如果当前值是日期对象
v = v.toISOString();//将日期对象转换成ISO格式字符串
} else if (utils.isObject(v)) {//又或者当前值是一个对象
v = JSON.stringify(v);//将对象字符串化
}
parts.push(encode(key) + '=' + encode(v));//parts数组里插入key=value的字符串
});
});
serializedParams = parts.join('&');//将字符串之间用&连成一整个字符串
}
if (serializedParams) {
var hashmarkIndex = url.indexOf('#');//url中hash标记位置
if (hashmarkIndex !== -1) {//如果含有hash标记就去掉
url = url.slice(0, hashmarkIndex);
}
url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams;//将基础url和查询字符串连接起来
}
return url;
};