axios源码学习
前言
本文是学习小白初学源码的自用笔记,参考自若川大佬的学习axios源码整体架构,打造属于自己的请求库
axios源码初始化
lib/axios.js主文件
axios.js主文件主要分为三部分组成:
第一部分:引入一些工具函数
utils,Axios构造函数、默认配置项defaults等。第二部分:生成实例对象
axios、axios.Axios、axios.create等第三部分:取消相关api实现,还有
all,spread,导出等实现
第一部分
引入一些工具函数utils,Axios构造函数、默认配置项defaults等
// 第一部分:
// lib/axios
// 严格模式
'use strict';
// 引入 utils 对象,有很多工具方法。
var utils = require('./utils');
// 引入 bind 方法
var bind = require('./helpers/bind');
// 核心构造函数 Axios
var Axios = require('./core/Axios');
// 合并配置方法
var mergeConfig = require('./core/mergeConfig');
// 引入默认配置
var defaults = require('./defaults');
第二部分
生成实例对象axios、axios.Axios、axios.create
/**
* Create an instance of Axios
*
* @param {Object} defaultConfig The default config for the instance
* @return {Axios} A new instance of Axios
*/
function createInstance(defaultConfig) {
// new 一个 Axios 生成实例对象
var context = new Axios(defaultConfig);
// bind 返回一个新的 wrap 函数,
// 也就是为什么调用 axios 是调用 Axios.prototype.request 函数的原因
var instance = bind(Axios.prototype.request, context);
// Copy axios.prototype to instance
// 复制 Axios.prototype 到实例上。
// 也就是为什么 有 axios.get 等别名方法,
// 且调用的是 Axios.prototype.get 等别名方法。
utils.extend(instance, Axios.prototype, context);
// Copy context to instance
// 复制 context 到 intance 实例
// 也就是为什么默认配置 axios.defaults 和拦截器 axios.interceptors 可以使用的原因
// 其实是new Axios().defaults 和 new Axios().interceptors
utils.extend(instance, context);
// 最后返回实例对象
return instance;
}
// Create the default instance to be exported
// 导出 创建默认实例
var axios = createInstance(defaults);
// Expose Axios class to allow class inheritance
// 暴露 Axios class 允许 class 继承 也就是可以 new axios.Axios()
// 但 axios 文档中 并没有提到这个,我们平时也用得少。
axios.Axios = Axios;
// Factory for creating new instances
// 工厂模式 创建新的实例 用户可以自定义一些参数
axios.create = function create(instanceConfig) {
return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
工具函数方法之bind
`axios/lib/helpers/bind.js
'use strict';
// 返回一个新的函数 wrap
module.exports = function bind(fn, thisArg) {
return function wrap() {
var args = new Array(arguments.length);
for (var i = 0; i < args.length; i++) {
args[i] = arguments[i];
}
// 把 argument 对象放在数组 args 里
return fn.apply(thisArg, args);
};
};
传递两个参数和thisArg指向,把参数arguments生成数组,最后调用返回参数结构
工具函数之extend
extend将b里面的属性和方法继承给a,并且将b里面的方法的执行上下文都绑定到thisArg
function extend(a, b, thisArg) {
forEach(b, function assignValue(val, key) {
if (thisArg && typeof val === 'function') {
a[key] = bind(val, thisArg);
} else {
a[key] = val;
}
});
return a;
}
工具函数之自定义forEach
自定义forEach方法遍历基本数据,数组,对象
/**
* @param {Object|Array} obj The object to iterate
* @param {Function} fn The callback to invoke for each item
*/
function forEach(obj, fn) {
// Don't bother if no value provided
// 判断 null 和 undefined 直接返回
if (obj === null || typeof obj === 'undefined') {
return;
}
// Force an array if not already something iterable
// 如果不是对象,放在数组里。
if (typeof obj !== 'object') {
/*eslint no-param-reassign:0*/
obj = [obj];
}
// 是数组 则用for 循环,调用 fn 函数。参数类似 Array.prototype.forEach 的前三个参数。
if (isArray(obj)) {
// Iterate over array values
for (var i = 0, l = obj.length; i < l; i++) {
fn.call(null, obj[i], i, obj);
}
} else {
// Iterate over object keys
// 用 for in 遍历对象,但 for in 会遍历原型链上可遍历的属性。
// 所以用 hasOwnProperty 来过滤自身属性了。
// 其实也可以用Object.keys来遍历,它不遍历原型链上可遍历的属性。
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
fn.call(null, obj[key], key, obj);
}
}
}
}
第三部分
取消相关API,还有all,spread、导出等实现
// Expose Cancel & CancelToken
// 导出 Cancel 和 CancelToken
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
// Expose all/spread
// 导出 all 和 spread API
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
// 也就是可以以下方式引入
// import axios from 'axios';
module.exports.default = axios;
spread方法
- 假设你有这样的需求:
function f(x,y,z){}
var args = [1,2,3]
f.apply(null,args)
- 那么就可以用
spread方法
axios.spread(function(x, y, z) {})([1, 2, 3]);
- 实现源码:
/**
* @param {Function} callback
* @returns {Function}
*/
module.exports = function spread(callback) {
return function wrap(arr) {
return callback.apply(null, arr);
};
};
核心构造函数Axios
axios/lib/core/Axios.js
function Axios(instanceConfig) {
// 默认参数
this.defaults = instanceConfig;
// 拦截器 请求和响应拦截器
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
Axios.prototype.request = function(config){
// 省略code
// code ...
var promise = Promise.resolve(config);
// code ...
return promise;
}
// 这是获取 Uri 的函数,这里省略
Axios.prototype.getUri = function(){}
// 提供一些请求方法的别名
// Provide aliases for supported request methods
// 遍历执行
// 也就是为啥我们可以 axios.get 等别名的方式调用,而且调用的是 Axios.prototype.request 方法
// 这个也在上面的 axios 结构图上有所体现。
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
源码实现:
构造含糊,handles用于存储拦截器函数
function InterceptorManager(){
this.handles = []
}
接下来声明了三个方法:使用、移除、遍历
InterceptorManager.prototype.use 使用
传递两个函数作为参数,数组中的一项存储的是{fulfilled: function(){}, rejected: function(){}}。返回数字 ID,用于移除拦截器。
/**js
* @param {Function} fulfilled The function to handle `then` for a `Promise`
* @param {Function} rejected The function to handle `reject` for a `Promise`
*
* @return {Number} 返回ID 是为了用 eject 移除
*/
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};
InterceptorManager.prototype.eject 移除
根据 use 返回的 ID 移除 拦截器。
/**
* @param {Number} id The ID that was returned by `use`
*/
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
};
有点类似定时器setTimeout 和 setInterval,返回值是id。用clearTimeout 和clearInterval来清除定时器。
// 提一下 定时器回调函数是可以传参的,返回值 timer 是数字
var timer = setInterval((name) => {
console.log(name);
}, 1000, 'aky');
console.log(timer); // 数字 ID
// 在控制台等会再输入执行这句,定时器就被清除了
clearInterval(timer);
InterceptorManager.prototype.forEach 遍历
/**
* @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);
}
});
};
实例结合
axios(options)
.then((res)=> {
response.innerHTML = JSON.stringify(res.data, null, 2);
})
.catch(function (res) {
response.innerHTML = JSON.stringify(res.data, null, 2);
});
先看调用栈流程
send Request按钮点击submit.onclcik- 调用
axios函数实际上调用axios.prototype.request函数,而这个函数使用bind返回一个名为warp的函数 - 调用
Axios.prototype.request - 有请求拦截器的情况下执行拦截器请求,中间会执行
dispatchRequest方法 5.dispatchRequest方法之后调用adapter (xhrAdapter)6.1. 最后调用Promise中的函数dispatchXhrRequest,(有响应拦截器的情况下最后会再调用响应拦截器)
Axios.prototype.request请求核心方法
这个函数是核心函数。 主要做了这几件事:
1.判断第一个参数是字符串,则设置 url,也就是支持
axios('example/url', [, config]),也支持axios({})。 2.合并默认参数和用户传递的参数 3.设置请求的方法,默认是是get方法 4.将用户设置的请求和响应拦截器、发送请求的dispatchRequest组成Promise链,最后返回还是Promise实例。 也就是保证了请求前拦截器先执行,然后发送请求,再响应拦截器执行这样的顺序。 也就是为啥最后还是可以then,catch方法的缘故。
Axios.prototype.request = function request(config) {
/*eslint no-param-reassign:0*/
// Allow for axios('example/url'[, config]) a la fetch API
// 这一段代码 其实就是 使 axios('example/url', [, config])
// config 参数可以省略
if (typeof config === 'string') {
config = arguments[1] || {};
config.url = arguments[0];
} else {
config = config || {};
}
// 合并默认参数和用户传递的参数
config = mergeConfig(this.defaults, config);
// Set config.method
// 设置 请求方法,默认 get 。
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
// 组成`Promise`链 这段拆开到后文再讲述
};
组成Promise链,返回Promise实例
这部分:用户设置的请求和响应拦截器、发送请求的
dispatchRequest组成Promise链。也就是保证了请求前拦截器先执行,然后发送请求,再响应拦截器执行这样的顺序 也就是保证了请求前拦截器先执行,然后发送请求,再响应拦截器执行这样的顺序 也就是为啥最后还是可以then,catch方法的缘故。
+=====================================未完待续====================================