axios源码分析(代码有删减,只针对主要过程)
一、axios的使用
// config对象
axios.request(url, config) || axios.request(config)
axios.get(url, config) || axios.get(config)
axios.delete(url, config) || axios.delete(config)
axios.head(url, config) || axios.head(config)
axios.options(url, config) || axios.options(config)
axios.post(url, data, config) || axios.post(config)
axios.put(url, data, config) || axios.put(config)
axios.patch(url, data, config) || axios.patch(config)
1、以上的请求方式实际上都是最后调用Axios的request方法
2、browser环境==>axios.get==>Axios.prototype.request==>dispatchRequest==>knownAdapters['xhr']==> new XMLHttpRequest()
3、node环境用的是knownAdapters['http']
4、用json-server来创建接口(json-server --watch db.json --delay 2000 延时2秒)
二、axios的创建过程(源码)
// node_modules\axios\lib\axios.js
import defaults from './defaults/index.js';
import Axios from './core/Axios.js';
function createInstance(defaultConfig) {
/**
* 依据默认配置defaults生成Axios的实例
* Axios上面有request和getUri原型方法
* ['delete', 'get', 'head', 'options']、['post', 'put', 'patch']用utils.forEach扩展到Axios.prototype上
* 以及defaults和interceptors实例属性
*/
const context = new Axios(defaultConfig);
/**
* instance为Axios.prototype.request的一个wrap函数(包装函数)
*/
const instance = bind(Axios.prototype.request, context);
/**
* 第一个utils.extend并不多余
* 因为其内部用于扩展的遍历forEach用的Object.getOwnPropertyNames(obj)和Object.keys(obj)
* 这2种对象遍历都不会将context上的Axios.prototype的属性给遍历
* 所以需要单独加Axios.prototype的扩展
*/
// 将Axios.prototype上的所有属性扩展到instance上
utils.extend(instance, Axios.prototype, context, { allOwnKeys: true });
// 将context上的属性扩展到instance上
utils.extend(instance, context, null, { allOwnKeys: true });
// 创建新instance的工厂函数(axios可以使用create方法创建新的instance)
instance.create = function create(instanceConfig) {
// 参数为默认参数和自定义参数的合并
return createInstance(mergeConfig(defaultConfig, instanceConfig));
};
return instance;
}
// 创建一个默认配置的instance,用于导出
const axios = createInstance(defaults);
// 下面扩展一些属性到axios上(instance和axios的区别就在这里)
axios.Axios = Axios;
// Expose Cancel & CancelToken
axios.CanceledError = CanceledError;
axios.CancelToken = CancelToken; // 取消请求
axios.isCancel = isCancel;
axios.VERSION = VERSION; // axios的版本
axios.toFormData = toFormData;
// Expose AxiosError class
axios.AxiosError = AxiosError;
// alias for CanceledError for backward compatibility
axios.Cancel = axios.CanceledError;
// Expose all/spread
axios.all = function all(promises) {
return Promise.all(promises);
};
axios.spread = spread;
// Expose isAxiosError
axios.isAxiosError = isAxiosError;
// Expose mergeConfig
axios.mergeConfig = mergeConfig;
axios.AxiosHeaders = AxiosHeaders;
axios.formToJSON = thing => formDataToJSON(utils.isHTMLForm(thing) ? new FormData(thing) : thing);
axios.HttpStatusCode = HttpStatusCode;
axios.default = axios;
// this module should only have a default export
export default axios
// node_modules\axios\lib\core\Axios.js
class Axios {
constructor(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
/**
* new InterceptorManager(),实例化InterceptorManager
* 得到的实例上有handlers实例属性和use、eject、clear、forEach原型方法
* 拦截器就是调用interceptors的request(response)上的use方法,将拥有2个回调的对象推进handlers数组中
* 在下面的request方法中,使用其对应forEach方法将上面的2个回调推入了InterceptorChain中
*/
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
request(configOrUrl, config) {
if (typeof configOrUrl === 'string') { // 针对第一个参数是路径字符串的情形,都转成config对象形式
config = config || {};
config.url = configOrUrl;
} else {
config = configOrUrl || {};
}
config = mergeConfig(this.defaults, config);
// 确定请求方法,默认为自定义的method,无就取默认配置的method,无就默认为get方法,并统一转化为小写
config.method = (config.method || this.defaults.method || 'get').toLowerCase();
let contextHeaders;
// Flatten headers
contextHeaders = headers && utils.merge(
headers.common,
headers[config.method]
);
contextHeaders && utils.forEach(
['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
(method) => {
delete headers[method];
}
);
config.headers = AxiosHeaders.concat(contextHeaders, headers);
const requestInterceptorChain = [];
/**
* 使请求拦截器requestInterceptorChain内的函数由while遍历进行同步执行
* 后面的dispatchRequest和responseInterceptorChain内的函数还是通过promise组成微任务进行异步执行
* 此时不需要在dispatchRequest后面加一个占位的undefined
*/
let synchronousRequestInterceptors = true;
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
return;
}
// 默认情况下 interceptor.synchronous ==> false
synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
// 将请求拦截器use函数的参数推进requestInterceptorChain数组
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
});
const responseInterceptorChain = [];
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
// 将响应拦截器use函数的参数推进responseInterceptorChain数组
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
let promise;
let i = 0;
let len;
if (!synchronousRequestInterceptors) {
// 请求拦截器requestInterceptorChain内的函数异步执行的话
const chain = [dispatchRequest.bind(this), undefined];
chain.unshift.apply(chain, requestInterceptorChain);
chain.push.apply(chain, responseInterceptorChain);
len = chain.length;
promise = Promise.resolve(config); // 返回一个成功的promise对象(第一个请求拦截器必定会执行成功的回调)
// axios拦截器interceptors的原理
/**
* Axios.prototype.request得到的是三个then的微任务函数
* 前两个是请求前的处理函数
* 中间是发送xhr请求和一个占位undefined,也就是在这个地方,开始了xhr的四部曲
* 最后是response拦截器的两个函数
*/
/**
* (1) 前一个then中的代码都是同步执行的,执行结束后第二个then即可注册进入微任务队列。
* (2) 当前一个then中有return 关键字,需要return的内容完全执行结束,第二个then才会注册进入微任务队列。
*/
while (i < len) {
promise = promise.then(chain[i++], chain[i++]);
}
return promise;
}
// 请求拦截器requestInterceptorChain内的函数同步执行的话
len = requestInterceptorChain.length;
let newConfig = config;
i = 0;
// 直接遍历执行requestInterceptorChain里面的onFulfilled函数
while (i < len) {
const onFulfilled = requestInterceptorChain[i++];
const onRejected = requestInterceptorChain[i++];
try {
newConfig = onFulfilled(newConfig);
} catch (error) {
onRejected.call(this, error);
break;
}
}
try {
// 这里传入了最新的经过请求拦截器处理的配置项newConfig
promise = dispatchRequest.call(this, newConfig);
} catch (error) {
return Promise.reject(error);
}
i = 0;
len = responseInterceptorChain.length;
while (i < len) {
promise = promise.then(responseInterceptorChain[i++], responseInterceptorChain[i++]);
}
return promise;
}
}
// 将['delete', 'get', 'head', 'options']内的方法扩展到Axios.prototype
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
Axios.prototype[method] = function(url, config) {
return this.request(mergeConfig(config || {}, {
method,
url,
data: (config || {}).data
}));
};
});
// 将['post', 'put', 'patch']内的方法扩展到Axios.prototype
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
function generateHTTPMethod(isForm) {
return function httpMethod(url, data, config) {
return this.request(mergeConfig(config || {}, {
method,
headers: isForm ? {
'Content-Type': 'multipart/form-data'
} : {},
url,
data
}));
};
}
Axios.prototype[method] = generateHTTPMethod();
});
export default Axios;
// node_modules\axios\lib\core\InterceptorManager.js
class InterceptorManager {
constructor() {
// 存放包含use方法的2个参数函数的对象的数组
this.handlers = [];
}
use(fulfilled, rejected, options) {
// 调用use方法,将拦截器的2个参数函数的对象推进handlers
this.handlers.push({
fulfilled,
rejected,
synchronous: options ? options.synchronous : false,
runWhen: options ? options.runWhen : null
});
return this.handlers.length - 1;
}
// 将某个拦截器的元素置为空
eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
}
// 清空handlers
clear() {
if (this.handlers) {
this.handlers = [];
}
}
// 将handlers里面的每个元素执行一遍forEachHandler
forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
}
}
export default InterceptorManager;
三、axios的创建过程(手写简化版)
function Axios(config) {
this.defaults = config;
this.interceptors = { // 简化拦截器
request: {},
response: {}
}
}
// 直接将原型方法写上去
Axios.prototype.request = function(config) { // 这里不要写成箭头函数,不然this为undefined,后面的bind也不起作用
console.log('发送ajax方法' + '请求的类型为' + config.method);
}
Axios.prototype.get = function(config) {
return this.request({method: 'get'});
}
Axios.prototype.post = function(config) {
return this.request({method: 'post'});
}
function createInstance(defaultsConfig) {
// 此时可以context.get()、context.post(),但是不能context()
let context = new Axios(defaultsConfig);
// 要想axios可以自调用,instance就应该是一个函数,此时instance不能.调用,因为上面没有相应的方法,需要进行扩展
let instance = Axios.prototype.request.bind(context);
Object.keys(Axios.prototype).forEach(ls => { // 将原型上的属性扩展上去
instance[ls] = Axios.prototype[ls].bind(context);
})
Object.keys(context).forEach(ls => { // 将defaults和interceptors扩展上去(Object.keys不会遍历到prototype上)
instance[ls] = context[ls];
})
return instance;
}
let axios = createInstance();
// axios({method: 'post'})
// axios.get();
// axios.post();
export default axios;
四、模拟实现axios发送请求(手写简化版)
// 1、声明构造函数
class Axios {
constructor(config) {
this.defaults = config;
this.interceptors = {
request: {},
response: {}
}
}
request(url, config) {
if (typeof url === 'string') {
config.url = url;
} else {
config = url;
}
// 创建一个成功的promise对象
let promise = Promise.resolve(config);
// 创建一个数组
let chains = [dispatchRequest, undefined]; // undefined占位
let len = chains.length;
let i = 0;
while(i < len) {
// 这里第一次循环由于上面返回成功的promise对象,所以一定会执行
promise = promise.then(chains[i++], chains[i++])
}
return promise;
}
}
// 2、dispatchRequest函数
function dispatchRequest(config) {
// 调用适配器发送请求
return xhrAdapter(config).then(res => {
// 对响应的结果进行转换
return JSON.parse(res.data)
}, err => {
console.log('err', err);
})
}
// 3、xhrAdapter适配器
function xhrAdapter(config) {
return new Promise((resolve, reject) => {
// 发送ajax请求
let xhr = new XMLHttpRequest();
xhr.open(config.method, config.url);
xhr.send();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
// 成功的状态
resolve({
config: config,
data: xhr.response,
headers: xhr.getAllResponseHeaders(),
request: xhr,
status: xhr.status,
statusText: xhr.statusText
});
} else {
// 失败的状态
reject(new Error('失败的状态码为' + xhr.status));
}
}
}
})
}
// 4、创建axios函数
let axios = Axios.prototype.request.bind(null);
axios({
method: 'get',
url: 'http://localhost:3000/goodsList'
}).then(res => {
console.log('res==>', res);
});
五、interceptor拦截器工作原理(手写简化版)
// 1、声明构造函数
class Axios {
constructor(config) {
this.defaults = config;
this.interceptors = {
request: new InterceptorManager(), // 主要提供了use方法来收集拦截器的执行函数
response: new InterceptorManager()
}
}
request(url, config) {
// 将多种形式的传参改为配置对象的形式
if (typeof url === 'string') {
config.url = url;
} else {
config = url;
}
// 创建一个成功的promise对象
let promise = Promise.resolve(config);
// 创建一个数组(里面放入真正的请求方法和一个占位的undefined)
let chains = [dispatchRequest.bind(this, config), undefined];
// 遍历请求拦截器里面的放有fulfilled和rejected函数的对象数组,将其unshift到chains的前面
if (this.interceptors.request.handlers.length) {
this.interceptors.request.handlers.forEach(ls => {
// [1, 2, 3].unshift(4, 5) ==> [4, 5, 1, 2, 3]
chains.unshift(ls.fulfilled, ls.rejected);
})
}
// 遍历响应拦截器里面的放有fulfilled和rejected函数的对象数组,将其push到chains的后面
if (this.interceptors.response.handlers.length) {
this.interceptors.response.handlers.forEach(ls => {
chains.push(ls.fulfilled, ls.rejected);
})
}
let len = chains.length;
let i = 0;
while(i < len) {
// 这里第一次循环由于上面返回成功的promise对象,所以一定会执行
// 然后组成promise.then链
// 前一个成功,后一个才可注册进入微任务队列
promise = promise.then(chains[i++], chains[i++])
}
return promise;
}
}
// 在Axios的原型上加上get和post方法
Axios.prototype.get = function(config) {
return this.request(config);
}
Axios.prototype.post = function(config) {
return this.request(config);
}
class InterceptorManager {
constructor() {
this.handlers = [];
}
use(fulfilled, rejected, options) {
this.handlers.push({
fulfilled,
rejected,
synchronous: options ? options.synchronous : false,
runWhen: options ? options.runWhen : null
})
}
eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
}
clear() {
if (this.handlers) {
this.handlers = [];
}
}
}
// 2、dispatchRequest函数
function dispatchRequest(config) {
// 调用适配器发送请求
return xhrAdapter(config).then(res => {
console.log('结果==>', JSON.parse(res.data));
// 对响应的结果进行转换
return JSON.parse(res.data);
}, err => {
console.log('err', err);
})
}
// 3、xhrAdapter适配器
function xhrAdapter(config) {
return new Promise((resolve, reject) => {
// 发送ajax请求
let xhr = new XMLHttpRequest();
xhr.open(config.method, config.url);
xhr.send();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
// 成功的状态
resolve({
config: config,
data: xhr.response,
headers: xhr.getAllResponseHeaders(),
request: xhr,
status: xhr.status,
statusText: xhr.statusText
});
} else {
// 失败的状态
reject(new Error('失败的状态码为' + xhr.status));
}
}
}
})
}
// 4、创建axios函数
function createInstance(config) {
let context = new Axios(config);
let instance = Axios.prototype.request.bind(context);
Object.keys(Axios.prototype).forEach(ls => {
instance[ls] = Axios.prototype[ls].bind(context);
})
Object.keys(context).forEach(ls => {
instance[ls] = context[ls];
})
return instance;
}
let axios = createInstance();
axios.interceptors.request.use(function requestFulfilledOne(config) {
console.log('request fulfilled One');
return config;
}, function requestRejectedOne() {
console.log('request rejected One');
})
axios.interceptors.request.use(function requestFulfilledTwo(config) {
console.log('request fulfilled Two');
return config;
}, function requestRejectedTwo() {
console.log('request rejected Two');
})
axios.interceptors.response.use(function responseFulfilledOne(response) {
console.log('response fulfilled One');
return response;
}, function responseRejectedOne() {
console.log('response rejected One');
})
axios.interceptors.response.use(function responseFulfilledTwo(response) {
console.log('response fulfilled Two');
return response;
}, function responseRejectedTwo() {
console.log('response rejected Two');
})
// axios({
// method: 'get',
// url: 'http://localhost:3000/goodsList'
// }).then(res => {
// console.log('res==>', res);
// });
axios.get({
method: 'get',
url: 'http://localhost:3000/goodsList'
}).then(res => {
console.log('res==>', res);
});
六、取消请求功能(手写简化版)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script>
window.onload = function () {
function Axios(config) {
this.config = config;
}
Axios.prototype.request = function (config) {
return dispatchRequest(config);
}
function dispatchRequest(config) {
return xhrAdapter(config);
}
function xhrAdapter(config) {
return new Promise((resolve, reject) => {
// 发送ajax请求
let xhr = new XMLHttpRequest();
xhr.open(config.method, config.url);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
// 成功的状态
resolve({
config: config,
data: xhr.response,
headers: xhr.getAllResponseHeaders(),
request: xhr,
status: xhr.status,
statusText: xhr.statusText
});
} else {
// 失败的状态
reject(new Error('失败的状态码为' + xhr.status));
}
}
}
if (config.cancelToken) {
config.cancelToken.promise.then(() => {
xhr.abort();
})
}
})
}
const context = new Axios({});
let axios = Axios.prototype.request.bind(context);
function CancelToken(executor) {
let resolvePromise;
this.promise = new Promise((resolve, reject) => {
resolvePromise = resolve;
})
executor(function () {
resolvePromise();
})
}
let cancel = null; // 设置变量
let dom1 = document.querySelector('#btn1');
let dom2 = document.querySelector('#btn2');
/**
* CancelToken的参数为executor,new操作执行CancelToken函数
* 执行executor时,将其的参数函数赋给cancel变量
* 所以cancel执行时,就会将CancelToken里面的this.promise置为成功的promise对象
* 此时xhrAdapter的config.cancelToken.promise.then就可以执行xhr.abort()来中止请求
*
*/
let cancelToken = new CancelToken(function(c) {
cancel = c;
});
dom1.onclick = function() { // 发起请求
axios({
method: 'get',
url: 'http://localhost:3000/goodsList',
cancelToken, // 设置取消请求的配置项
}).then(res => {
cancel = null;
console.log('res===>', res);
})
}
dom2.onclick = function () { // 取消请求
if (cancel !== null) {
cancel();
}
}
}
</script>
</head>
<body>
<button id="btn1">发起请求</button>
<button id="btn2">取消请求</button>
</body>
</html>
七、相关问题
1、axios和Axios的关系?
1、从语法上来说,axios不是Axios的实例
2、在功能上,axios是Axios的实例
3、axios是Axios.prototype.request函数经过bind返回的函数
4、axios作为对象,有Axios原型上的所有方法,也有Axios对象上的所有属性
2、instance和axios的区别?
相同点:
1、都是一个可以发起任意请求的函数
2、都有发特定请求的各种方法
3、都有默认配置defaults和拦截器属性interceptors
不同点:
1、默认配置可能不一样
2、axios在instance的基础上多了很多配置(CancelToken、all......)
3、axios的整体运行流程
发起请求:axios() ===> request(config) ===> dispatchRequest(config) ===> xhrAdapter(config)
数据响应:xhrAdapter(config) ===> dispatchRequest(config) ===> request(config) ===> axios().then(res => {})
4、axios的请求和响应拦截器是什么?
请求拦截器:
1、是真正发送请求前执行的回调函数
2、可对请求进行检查或配置进行特定处理
3、成功的回调函数,传递的默认是config
4、成功的回调函数,传递的默认是config
响应拦截器:
1、在请求得到响应后执行的回调函数
2、可以对响应数据进行特定处理
3、成功的回调函数,传递的默认是response
4、成功的回调函数,传递的默认是error
5、axios的请求和响应数据转化器是什么?
请求转换器:对请求头和请求体数据进行特定处理的函数
if (utils.isObject(data)) {
setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
return JSON.stringify(data);
}
响应转换器:将响应体json字符串解析为js对象或者数组的函数
response.data = JSON.parse(response.data);
6、response的整体结构
{
data,
status,
statusText
headers,
config,
request
}
7、error的整体结构
{
message,
response,
request
}