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;
});
整体流程图
为什么把流程图放最后而不是放前面,因为这个图 不了解源码看了跟没看一样。