Axios源码分析&自定义Axios
1 源码分析
1.1 源码目录结构
javascript
├── /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/ # 一些辅助方法
│ ├── defaults/ # axios的默认配置
│ ├── axios.js # 对外暴露接口
│ └── utils.js # 公用工具
├── package.json # 项目信息
├── index.d.ts # 配置TypeScript的声明文件
└── index.js # 入口文件
1.2 axios 运行流程图
1.3 拦截器原理图
2 自定义 axios
2.1 创建 axios 函数
**axios 函数本质不是 Axios 的实例,调用 axios 函数其实就是调用 Axios.prototype.request() **
javascript
1 定义Axios类
1.1 设置构造器 constructor(instanceConfig) 方法,参数是实例的配置项,无返回,设置实例的默认属性
1.2 设置发送请求 Axios.prototype.request()
2 定义 createInstance(defaultConfig) 函数 ,参数是默认配置项,返回一个指定this的 axios() 函数
2.1 定义一个bind(fn,arr)函数,参数是 (对象,伪数组),返回一个指定this后的函数
js
// 定义默认请求配置项
const defaults = {
timeout: 0
};
// 核心类
class Axios {
// 构造器方法
constructor(instanceConfig) {
this.defaults = instanceConfig;
}
// 发送请求的方法
request() {
console.log('request');
}
}
function bind(fn, thisArg) {
return function wrap() {
// fn 就是 Axios.prototype.request
// argments 是伪数组,成员是传递给 axios 函数的参数
// 就是在调用 Axios.prototype.request, 并设置里面的this是Axios类的一个实例
return fn.apply(thisArg, arguments)
}
}
/**
* 创建 axios 函数, axios函数本质不是 Axios 的实例
* @param {Object} defaultConfig The default config for the instance
* @returns {Function} 调用该函数就是调用Axios.prototype.request()
*/
function createInstance(defaultConfig) {
// 实例化核心类型 得到实例
const context = new Axios(defaultConfig);
// 创建 axios 函数
const instance = bind(Axios.prototype.request, context);
// 返回
return instance;
}
// 创建 axios
const axios = createInstance(defaults);
// 以模块的形式对外暴露
export default axios;
2.2 发送请求并返回 Promise
javascript
1. Axios.prototype.request 有一个或两个参数
2. 将发送请求时传入的配置对象和全局配置对象合并
3. 设置默认请求方式是 GET
4. 将发送请求的操作封装 dispatchRequest 函数中, 函数返回 Promise 对象
5. request 中调用 return dispathRequest()
js
// 定义默认请求配置项
const defaults = {
timeout: 0
};
/**
* 发送 ajax 请求
* @param {object} config 请求配置项
* @returns {Promise}
*/
function dispatchRequest(config) {
return new Promise((resolve, reject) => {
// 创建 xhr 对象
const xhr = new XMLHttpRequest();
// 初始化
xhr.open(config.method, config.url);
// 发送
xhr.send();
// 监听成功响应
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve('Success');
} else {
reject('Error');
}
}
// 监听失败响应
xhr.onerror = () => {
reject('Error');
}
});
}
// 核心类
class Axios {
// 构造器方法
constructor(instanceConfig) {
this.defaults = instanceConfig;
}
/**
* 发送 ajax 请求
* @param {String|Object} configOrUrl url或者请求配置对象
* @param {?Object} config 请求配置对象,如果第一个参数已经是对象,不需要该参数
* @returns {Promise}
*/
request(configOrUrl={}, config={}) {
// 判断第一个参数是否是 url
if (typeof configOrUrl === 'string') {
config.url = configOrUrl
} else {
config = configOrUrl;
}
// 将传入的请求配置对象和全局请求配置对象合并
config = Object.assign({}, this.defaults, config);
// 设置默认请求方式是 GET
config.method = (config.method || this.defaults.method || 'get').toUpperCase();
// 调用函数发送请求
return dispatchRequest.call(this, config);
}
}
function bind(fn, thisArg) {
return function wrap() {
// fn 就是 Axios.prototype.request
// argments 是伪数组,成员是传递给 axios 函数的参数
// 就是在调用 Axios.prototype.request, 并设置里面的this是Axios类的一个实例
return fn.apply(thisArg, arguments)
}
}
/**
* 创建 axios 函数, axios函数本质不是 Axios 的实例
* @param {Object} defaultConfig The default config for the instance
* @returns {Function} 调用该函数就是调用Axios.prototype.request()
*/
function createInstance(defaultConfig) {
// 实例化核心类型 得到实例
const context = new Axios(defaultConfig);
// 创建 axios 函数
const instance = bind(Axios.prototype.request, context);
// 返回
return instance;
}
// 创建 axios
const axios = createInstance(defaults);
// 以模块的形式对外暴露
export default axios;
2.3 请求配置项设置
javascript
1 dispatchRequest(config)发送请求函数中
设置请求头,
2 dispatchRequest(config)发送请求函数中
设置请求体,判断post才能发请求体,并设置content-type
3 Ajax.prototype.request() 方法中设置
合并Baseurl和url,searchParams 拼接到 url 后面,
4 给 axios函数 设置全局配置的属性
instance.defaults = context.defaults;
javascript
1. baseURL
2. params URL参数
3. responseType 响应类型
4. headers 请求头
5. data 请求体
6. 全局配置项
js
// 定义默认请求配置项
const defaults = {
timeout: 0,
responseType: 'json'
};
/**
* 发送 ajax 请求
* @param {object} config 请求配置项
* @returns {Promise}
*/
function dispatchRequest(config) {
return new Promise((resolve, reject) => {
// 创建 xhr 对象
const xhr = new XMLHttpRequest();
// 设置响应类型
xhr.responseType = config.responseType;
// 初始化
xhr.open(config.method, config.url);
// 设置请求头 xhr.setRequestHeader(key, value)
if (config.headers) {
for (let key in config.headers) {
xhr.setRequestHeader(key, config.headers[key]);
}
}
// 设置请求体
let body;
// 只有允许的请求方法,才可以携带请求体
if (['POST', 'PUT', 'PATCH'].includes(config.method)) {
if (typeof config.data === 'string') {
body = config.data;
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
} else if (Object.prototype.toString.call(config.data) === '[object Object]') {
body = JSON.stringify(config.data);
xhr.setRequestHeader('Content-type', 'application/json');
} else {
body = config.data;
}
}
// 发送
xhr.send(body);
// 监听成功响应
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve('Success');
} else {
reject('Error');
}
}
// 监听失败响应
xhr.onerror = () => {
reject('Error');
}
});
}
// 核心类
class Axios {
// 构造器方法
constructor(instanceConfig) {
this.defaults = instanceConfig;
}
/**
* 发送 ajax 请求
* @param {String|Object} configOrUrl url或者请求配置对象
* @param {?Object} config 请求配置对象,如果第一个参数已经是对象,不需要该参数
* @returns {Promise}
*/
request(configOrUrl={}, config={}) {
// 判断第一个参数是否是 url
if (typeof configOrUrl === 'string') {
config.url = configOrUrl
} else {
config = configOrUrl;
}
// 将传入的请求配置对象和全局请求配置对象合并
config = Object.assign({}, this.defaults, config);
// 设置默认请求方式是 GET
config.method = (config.method || this.defaults.method || 'get').toUpperCase();
// 合并 baseURL 和 url
if (config.baseURL && !config.url.startsWith('http://') && !config.url.startsWith('https://')) {
config.url = config.baseURL + config.url;
}
// 将 searchParams 拼接到 url 后面
if (config.params) {
config.url += '?' + Object.entries(config.params).map(item=>item[0]+'='+item[1]).join('&');
}
// 调用函数发送请求
return dispatchRequest.call(this, config);
}
}
function bind(fn, thisArg) {
return function wrap() {
// fn 就是 Axios.prototype.request
// argments 是伪数组,成员是传递给 axios 函数的参数
// 就是在调用 Axios.prototype.request, 并设置里面的this是Axios类的一个实例
return fn.apply(thisArg, arguments)
}
}
/**
* 创建 axios 函数, axios函数本质不是 Axios 的实例
* @param {Object} defaultConfig The default config for the instance
* @returns {Function} 调用该函数就是调用Axios.prototype.request()
*/
function createInstance(defaultConfig) {
// 实例化核心类型 得到实例
const context = new Axios(defaultConfig);
// 创建 axios 函数
const instance = bind(Axios.prototype.request, context);
// 给 instance 设置全局配置接口
instance.defaults = context.defaults;
// 返回
return instance;
}
// 创建 axios
const axios = createInstance(defaults);
// 以模块的形式对外暴露
export default axios;
2.4 响应结果处理
javascript
1 dispatchRequest(config)发送请求函数中
设置响应类型 xhr.responseType = config.responseType;
监听成功/失败的响应 xhr.onload xhr.onerr
下方是axios返回结果示例
javascript
成功的结果
{
config
data
headers
request
status
statusText
}
请求发送未成功的失败
{
code: "ERR_NETWORK"
config
message: "ERR_NETWORK"
name: "AxiosError"
request
}
响应不正确,如404
{
code: "ERR_BAD_REQUEST"
config
message: "Request failed with status code 404"
name: "AxiosError"
request
}
js
// 定义默认请求配置项
const defaults = {
timeout: 0,
responseType: 'json'
};
/**
* 发送 ajax 请求
* @param {object} config 请求配置项
* @returns {Promise}
*/
function dispatchRequest(config) {
return new Promise((resolve, reject) => {
// 创建 xhr 对象
const xhr = new XMLHttpRequest();
// 设置响应类型
xhr.responseType = config.responseType;
// 初始化
xhr.open(config.method, config.url);
// 设置请求头 xhr.setRequestHeader(key, value)
if (config.headers) {
for (let key in config.headers) {
xhr.setRequestHeader(key, config.headers[key]);
}
}
// 设置 请求体
let body;
// 只有允许的请求方法,才可以携带请求体
if (['POST', 'PUT', 'PATCH'].includes(config.method)) {
if (typeof config.data === 'string') {
body = config.data;
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
} else if (Object.prototype.toString.call(config.data) === '[object Object]') {
body = JSON.stringify(config.data);
xhr.setRequestHeader('Content-type', 'application/json');
} else {
body = config.data;
}
}
// 发送
xhr.send(body);
// 监听成功响应
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve({
config,
request: xhr,
data: xhr.response,
statsu: xhr.status,
statusText: xhr.statusText,
headers: xhr.getAllResponseHeaders().split('\r\n').filter(item=>item).map(item=>item.split(': ')).reduce((pre,item)=>({...pre, [item[0]]:item[1]}), {})
});
} else {
reject({
code: "ERR_BAD_REQUEST",
config,
message: "Request failed with status code " + xhr.status,
name: "AxiosError",
request: xhr
});
}
}
// 监听失败响应
xhr.onerror = () => {
reject({
code: "ERR_NETWORK",
config,
message: "ERR_NETWORK",
name: "AxiosError",
request: xhr
});
}
});
}
// 核心类
class Axios {
// 构造器方法
constructor(instanceConfig) {
this.defaults = instanceConfig;
}
/**
* 发送 ajax 请求
* @param {String|Object} configOrUrl url或者请求配置对象
* @param {?Object} config 请求配置对象,如果第一个参数已经是对象,不需要该参数
* @returns {Promise}
*/
request(configOrUrl={}, config={}) {
// 判断第一个参数是否是 url
if (typeof configOrUrl === 'string') {
config.url = configOrUrl
} else {
config = configOrUrl;
}
// 将传入的请求配置对象和全局请求配置对象合并
config = Object.assign({}, this.defaults, config);
// 设置默认请求方式是 GET
config.method = (config.method || this.defaults.method || 'get').toUpperCase();
// 合并 baseURL 和 url
if (config.baseURL && !config.url.startsWith('http://') && !config.url.startsWith('https://')) {
config.url = config.baseURL + config.url;
}
// 将 searchParams 拼接到 url 后面
if (config.params) {
config.url += '?' + Object.entries(config.params).map(item=>item[0]+'='+item[1]).join('&');
}
// 调用函数发送请求
return dispatchRequest.call(this, config);
}
}
function bind(fn, thisArg) {
return function wrap() {
// fn 就是 Axios.prototype.request
// argments 是伪数组,成员是传递给 axios 函数的参数
// 就是在调用 Axios.prototype.request, 并设置里面的this是Axios类的一个实例
return fn.apply(thisArg, arguments)
}
}
/**
* 创建 axios 函数, axios函数本质不是 Axios 的实例
* @param {Object} defaultConfig The default config for the instance
* @returns {Function} 调用该函数就是调用Axios.prototype.request()
*/
function createInstance(defaultConfig) {
// 实例化核心类型 得到实例
const context = new Axios(defaultConfig);
// 创建 axios 函数
const instance = bind(Axios.prototype.request, context);
// 给 instance 设置全局配置接口
instance.defaults = context.defaults;
// 返回
return instance;
}
// 创建 axios
const axios = createInstance(defaults);
// 以模块的形式对外暴露
export default axios;
2.5 超时设置
javascript
1 发送ajax请求时,配置请求配置项 timeout
2 在dispatchRequest函数中给xhr对象添加timeout属性
3 监听timeout事件
javascript
{
code: "ECONNABORTED"
config
message: "timeout of 2000ms exceeded"
name: "AxiosError"
request
}
js
// 定义默认请求配置项
const defaults = {
timeout: 0,
responseType: 'json'
};
/**
* 发送 ajax 请求
* @param {object} config 请求配置项
* @returns {Promise}
*/
function dispatchRequest(config) {
return new Promise((resolve, reject) => {
// 创建 xhr 对象
const xhr = new XMLHttpRequest();
// 设置响应类型
xhr.responseType = config.responseType;
// 设置超时时间
xhr.timeout = config.timeout;
// 初始化
xhr.open(config.method, config.url);
// 设置请求头 xhr.setRequestHeader(key, value)
if (config.headers) {
for (let key in config.headers) {
xhr.setRequestHeader(key, config.headers[key]);
}
}
// 设置 请求体
let body;
// 只有允许的请求方法,才可以携带请求体
if (['POST', 'PUT', 'PATCH'].includes(config.method)) {
if (typeof config.data === 'string') {
body = config.data;
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
} else if (Object.prototype.toString.call(config.data) === '[object Object]') {
body = JSON.stringify(config.data);
xhr.setRequestHeader('Content-type', 'application/json');
} else {
body = config.data;
}
}
// 发送
xhr.send(body);
// 监听成功响应
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
// console.log(xhr.getAllResponseHeaders().split('\r\n').filter(item=>item))
// console.log(xhr.getAllResponseHeaders().split('\r\n').filter(item=>item).map(item=>item.split(': ')));
// console.log(xhr.getAllResponseHeaders().split('\r\n').filter(item=>item).map(item=>item.split(': ')).reduce((pre,item)=>({...pre, [item[0]]:item[1]}), {}));
resolve({
config,
request: xhr,
data: xhr.response,
statsu: xhr.status,
statusText: xhr.statusText,
headers: xhr.getAllResponseHeaders().split('\r\n').filter(item=>item).map(item=>item.split(': ')).reduce((pre,item)=>({...pre, [item[0]]:item[1]}), {})
});
} else {
reject({
code: "ERR_BAD_REQUEST",
config,
message: "Request failed with status code " + xhr.status,
name: "AxiosError",
request: xhr
});
}
}
// 监听失败响应
xhr.onerror = () => {
reject({
code: "ERR_NETWORK",
config,
message: "ERR_NETWORK",
name: "AxiosError",
request: xhr
});
}
// 超时的事件
xhr.ontimeout = () => {
reject({
code: "ECONNABORTED",
config,
message: `timeout of ${config.timeout}ms exceeded`,
name: "AxiosError",
request:xhr
})
}
});
}
// 核心类
class Axios {
// 构造器方法
constructor(instanceConfig) {
this.defaults = instanceConfig;
}
/**
* 发送 ajax 请求
* @param {String|Object} configOrUrl url或者请求配置对象
* @param {?Object} config 请求配置对象,如果第一个参数已经是对象,不需要该参数
* @returns {Promise}
*/
request(configOrUrl={}, config={}) {
// 判断第一个参数是否是 url
if (typeof configOrUrl === 'string') {
config.url = configOrUrl
} else {
config = configOrUrl;
}
// 将传入的请求配置对象和全局请求配置对象合并
config = Object.assign({}, this.defaults, config);
// 设置默认请求方式是 GET
config.method = (config.method || this.defaults.method || 'get').toUpperCase();
// 合并 baseURL 和 url
if (config.baseURL && !config.url.startsWith('http://') && !config.url.startsWith('https://')) {
config.url = config.baseURL + config.url;
}
// 将 searchParams 拼接到 url 后面
if (config.params) {
config.url += '?' + Object.entries(config.params).map(item=>item[0]+'='+item[1]).join('&');
}
// 调用函数发送请求
return dispatchRequest.call(this, config);
}
}
function bind(fn, thisArg) {
return function wrap() {
// fn 就是 Axios.prototype.request
// argments 是伪数组,成员是传递给 axios 函数的参数
// 就是在调用 Axios.prototype.request, 并设置里面的this是Axios类的一个实例
return fn.apply(thisArg, arguments)
}
}
/**
* 创建 axios 函数, axios函数本质不是 Axios 的实例
* @param {Object} defaultConfig The default config for the instance
* @returns {Function} 调用该函数就是调用Axios.prototype.request()
*/
function createInstance(defaultConfig) {
// 实例化核心类型 得到实例
const context = new Axios(defaultConfig);
// 创建 axios 函数
const instance = bind(Axios.prototype.request, context);
// 给 instance 设置全局配置接口
instance.defaults = context.defaults;
// 返回
return instance;
}
// 创建 axios
const axios = createInstance(defaults);
// 以模块的形式对外暴露
export default axios;
2.6 取消请求
javascript
取消请求的步骤:
1. 提前定义变量用于存储取消函数
2. 请求配置中 设置 cancelToken, 它的值是 axios.CancelToken 的实例, 是一个 Promise 对象,将能够改变该 Promise状态的函数赋值给前面的变量
3. 需要的时候调用取消函数, 本质上改变Promise对象状态,状态一改,执行取消
注意: axios.isCancel(error) 判断错误是否是因为取消请求
js
{
code: "ERR_CANCELED"
message: "canceled"
name: "CanceledError"
}
javascript
执行流程:(调用了取消函数之后 发生了什么)
1. 调用取消函数
2. config.cancelToken 状态改变
3. 执行了 then 中的回调,回调里 xhr.abort
4. xhr.onabort 事件触发
html
<script type="module">
// 引入 axios 模块
import axios from './axios_bak.js';
// 获取元素
const btns = document.querySelectorAll('button');
// 设置全局配置项
axios.defaults.baseURL = 'http://127.0.0.1:8080';
// 定义变量 存储取消函数
let cancel = null;
// 创建promise对象,将能改变状态的函数赋值给了cancle
const cancelToken = new axios.CancelToken(resolve=> {
cancel = resolve;
});
// 点击发送请求
btns[0].onclick = () => {
console.log();
axios({
method: 'get',
url: '/hololive/1',
cancelToken
})
.then(val => {
console.log('成功:', val);
})
.catch(err => {
console.log('失败:', err);
console.log(axios.isCancel(err))
})
};
// 点击取消请求
btns[1].onclick = () => {
cancel();
}
</script>
js
// 定义默认请求配置项
const defaults = {
timeout: 0,
responseType: 'json'
};
/**
* 发送 ajax 请求
* @param {object} config 请求配置项
* @returns {Promise}
*/
function dispatchRequest(config) {
return new Promise((resolve, reject) => {
// 创建 xhr 对象
const xhr = new XMLHttpRequest();
// 设置响应类型
xhr.responseType = config.responseType;
// 设置超时时间
xhr.timeout = config.timeout;
// 初始化
xhr.open(config.method, config.url);
// 设置请求头 xhr.setRequestHeader(key, value)
if (config.headers) {
for (let key in config.headers) {
xhr.setRequestHeader(key, config.headers[key]);
}
}
// 设置 请求体
let body;
// 只有允许的请求方法,才可以携带请求体
if (['POST', 'PUT', 'PATCH'].includes(config.method)) {
if (typeof config.data === 'string') {
body = config.data;
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
} else if (Object.prototype.toString.call(config.data) === '[object Object]') {
body = JSON.stringify(config.data);
xhr.setRequestHeader('Content-type', 'application/json');
} else {
body = config.data;
}
}
// 设置取消请求的Promise对象状态改变
config.cancelToken.then(() => {
xhr.abort(); // 取消请求
})
// 发送
xhr.send(body);
// 监听成功响应
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
// console.log(xhr.getAllResponseHeaders().split('\r\n').filter(item=>item))
// console.log(xhr.getAllResponseHeaders().split('\r\n').filter(item=>item).map(item=>item.split(': ')));
// console.log(xhr.getAllResponseHeaders().split('\r\n').filter(item=>item).map(item=>item.split(': ')).reduce((pre,item)=>({...pre, [item[0]]:item[1]}), {}));
resolve({
config,
request: xhr,
data: xhr.response,
statsu: xhr.status,
statusText: xhr.statusText,
headers: xhr.getAllResponseHeaders().split('\r\n').filter(item=>item).map(item=>item.split(': ')).reduce((pre,item)=>({...pre, [item[0]]:item[1]}), {})
});
} else {
reject({
code: "ERR_BAD_REQUEST",
config,
message: "Request failed with status code " + xhr.status,
name: "AxiosError",
request: xhr
});
}
}
// 监听失败响应
xhr.onerror = () => {
reject({
code: "ERR_NETWORK",
config,
message: "ERR_NETWORK",
name: "AxiosError",
request: xhr
});
}
// 超时的事件
xhr.ontimeout = () => {
reject({
code: "ECONNABORTED",
config,
message: `timeout of ${config.timeout}ms exceeded`,
name: "AxiosError",
request:xhr
})
}
// 监听取消请求的事件
xhr.onabort = () => {
reject({
code: "ERR_CANCELED",
message: "canceled",
name: "CanceledError"
})
}
});
}
// 核心类
class Axios {
// 构造器方法
constructor(instanceConfig) {
this.defaults = instanceConfig;
}
/**
* 发送 ajax 请求
* @param {String|Object} configOrUrl url或者请求配置对象
* @param {?Object} config 请求配置对象,如果第一个参数已经是对象,不需要该参数
* @returns {Promise}
*/
request(configOrUrl={}, config={}) {
// 判断第一个参数是否是 url
if (typeof configOrUrl === 'string') {
config.url = configOrUrl
} else {
config = configOrUrl;
}
// 将传入的请求配置对象和全局请求配置对象合并
config = Object.assign({}, this.defaults, config);
// 设置默认请求方式是 GET
config.method = (config.method || this.defaults.method || 'get').toUpperCase();
// 合并 baseURL 和 url
if (config.baseURL && !config.url.startsWith('http://') && !config.url.startsWith('https://')) {
config.url = config.baseURL + config.url;
}
// 将 searchParams 拼接到 url 后面
if (config.params) {
config.url += '?' + Object.entries(config.params).map(item=>item[0]+'='+item[1]).join('&');
}
// 调用函数发送请求
return dispatchRequest.call(this, config);
}
}
function bind(fn, thisArg) {
return function wrap() {
// fn 就是 Axios.prototype.request
// argments 是伪数组,成员是传递给 axios 函数的参数
// 就是在调用 Axios.prototype.request, 并设置里面的this是Axios类的一个实例
return fn.apply(thisArg, arguments)
}
}
/**
* 创建 axios 函数, axios函数本质不是 Axios 的实例
* @param {Object} defaultConfig The default config for the instance
* @returns {Function} 调用该函数就是调用Axios.prototype.request()
*/
function createInstance(defaultConfig) {
// 实例化核心类型 得到实例
const context = new Axios(defaultConfig);
// 创建 axios 函数
const instance = bind(Axios.prototype.request, context);
// 给 instance 设置全局配置接口
instance.defaults = context.defaults;
// 返回
return instance;
}
// 创建 axios
const axios = createInstance(defaults);
// 给 axios 设置属性
axios.CancelToken = Promise;
axios.isCancel = (err) => {
return err.code === "ERR_CANCELED";
}
// 以模块的形式对外暴露
export default axios;
2.9 get()、post() 方法
javascript
1. 将 Axios 实例(context)上的属性都添加到 axios 上
2. 将 Axios 实例原型(Axios.prototype)上的方法都添加到 axios 上
js
// 定义默认请求配置项
const defaults = {
timeout: 0,
responseType: 'json'
};
/**
* 发送 ajax 请求
* @param {object} config 请求配置项
* @returns {Promise}
*/
function dispatchRequest(config) {
return new Promise((resolve, reject) => {
// 创建 xhr 对象
const xhr = new XMLHttpRequest();
// 设置响应类型
xhr.responseType = config.responseType;
// 设置超时时间
xhr.timeout = config.timeout;
// 初始化
xhr.open(config.method, config.url);
// 设置请求头 xhr.setRequestHeader(key, value)
if (config.headers) {
for (let key in config.headers) {
xhr.setRequestHeader(key, config.headers[key]);
}
}
// 设置 请求体
let body;
// 只有允许的请求方法,才可以携带请求体
if (['POST', 'PUT', 'PATCH'].includes(config.method)) {
if (typeof config.data === 'string') {
body = config.data;
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
} else if (Object.prototype.toString.call(config.data) === '[object Object]') {
body = JSON.stringify(config.data);
xhr.setRequestHeader('Content-type', 'application/json');
} else {
body = config.data;
}
}
// 设置取消请求的Promise对象状态改变
if (config.cancelToken) {
config.cancelToken.then(() => {
xhr.abort(); // 取消请求
});
}
// 发送
xhr.send(body);
// 监听成功响应
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve({
config,
request: xhr,
data: xhr.response,
statsu: xhr.status,
statusText: xhr.statusText,
headers: xhr.getAllResponseHeaders().split('\r\n').filter(item=>item).map(item=>item.split(': ')).reduce((pre,item)=>({...pre, [item[0]]:item[1]}), {})
});
} else {
reject({
code: "ERR_BAD_REQUEST",
config,
message: "Request failed with status code " + xhr.status,
name: "AxiosError",
request: xhr
});
}
}
// 监听失败响应
xhr.onerror = () => {
reject({
code: "ERR_NETWORK",
config,
message: "ERR_NETWORK",
name: "AxiosError",
request: xhr
});
}
// 超时的事件
xhr.ontimeout = () => {
reject({
code: "ECONNABORTED",
config,
message: `timeout of ${config.timeout}ms exceeded`,
name: "AxiosError",
request:xhr
})
}
// 监听取消请求的事件
xhr.onabort = () => {
reject({
code: "ERR_CANCELED",
message: "canceled",
name: "CanceledError"
})
}
});
}
// 核心类
class Axios {
// 构造器方法
constructor(instanceConfig) {
this.defaults = instanceConfig;
}
/**
* 发送 ajax 请求
* @param {String|Object} configOrUrl url或者请求配置对象
* @param {?Object} config 请求配置对象,如果第一个参数已经是对象,不需要该参数
* @returns {Promise}
*/
request(configOrUrl={}, config={}) {
// 判断第一个参数是否是 url
if (typeof configOrUrl === 'string') {
config.url = configOrUrl
} else {
config = configOrUrl;
}
// 将传入的请求配置对象和全局请求配置对象合并
config = Object.assign({}, this.defaults, config);
// 设置默认请求方式是 GET
config.method = (config.method || this.defaults.method || 'get').toUpperCase();
// 合并 baseURL 和 url
if (config.baseURL && !config.url.startsWith('http://') && !config.url.startsWith('https://')) {
config.url = config.baseURL + config.url;
}
// 将 searchParams 拼接到 url 后面
if (config.params) {
config.url += '?' + Object.entries(config.params).map(item=>item[0]+'='+item[1]).join('&');
}
// 调用函数发送请求
return dispatchRequest.call(this, config);
}
get(url, config={}) {
return this.request(url, {
...config,
method: 'GET'
});
}
post(url, data, config={}) {
return this.request(url, {
...config,
data,
method: 'POST'
});
}
}
function bind(fn, thisArg) {
return function wrap() {
// fn 就是 Axios.prototype.request
// argments 是伪数组,成员是传递给 axios 函数的参数
// 就是在调用 Axios.prototype.request, 并设置里面的this是Axios类的一个实例
return fn.apply(thisArg, arguments)
}
}
/**
* 创建 axios 函数, axios函数本质不是 Axios 的实例
* @param {Object} defaultConfig The default config for the instance
* @returns {Function} 调用该函数就是调用Axios.prototype.request()
*/
function createInstance(defaultConfig) {
// 实例化核心类型 得到实例
const context = new Axios(defaultConfig);
// 创建 axios 函数
const instance = bind(Axios.prototype.request, context);
// 给 instance 设置全局配置接口
// instance.defaults = context.defaults;
// 将 Axios 实例自身的所有属性都添加到 axios 上
for (let key in context) {
instance[key] = context[key];
}
// 将 Axios 实例的原型上所有属性都添加到 axios 上
//console.log(Axios.prototype.request);
Object.getOwnPropertyNames(Axios.prototype).forEach(key => {
instance[key] = Axios.prototype[key].bind(context);
});
// 返回
return instance;
}
// 创建 axios
const axios = createInstance(defaults);
// 给 axios 设置属性
axios.CancelToken = Promise;
axios.isCancel = (err) => {
return err.code === "ERR_CANCELED";
}
// 以模块的形式对外暴露
export default axios;
2.8 实现拦截器
思路
javascript
1 定义拦截类 InterceptorManager
构造器函数 constructor() 设置实例的属性 handlers 为一个空数组,记录实例的请求和响应
use方法 给 this.handlers 添加一个对象作为元素,对象中同一个拦截器的两个函数
2 在Ajax.prototype.request()方法中设置 在调用请求发送前 设置 发送队列
将所有的请求拦截器函数加到前面
将所有的响应拦截器函数加到后面
设置 起始 promise 对象
shift() 取出第一个元素后,会从数组中删掉
javascript
1 ---// 定义拦截器类---
// 拦截器类
class InterceptorManager {
constructor() {
this.handlers = [];
}
use(onResolved, onRejected) {
// 给 this.handlers 添加一个对象作为元素,对象中同一个拦截器的两个函数
this.handlers.push({
onResolved,
onRejected
});
// 返回最后一个索引的元素(刚刚push进去的元素)
return this.handlers.length-1;
}
eject() {
}
}
2 ---// 调用函数发送请求---
// 创建一个执行链 将发ajax请求的函数加入
const chain = [[dispatchRequest.bind(this, config), undefined]];
// 将所有的请求拦截器函数加到前面
this.interceptors.request.handlers.forEach(item => {
chain.unshift([item.onResolved, item.onRejected]);
})
// 将所有的响应拦截器函数加到后面
this.interceptors.response.handlers.forEach(item => {
chain.push([item.onResolved, item.onRejected]);
});
// 起始 promise 对象
let p = Promise.resolve(config);
while (chain.length) {
// shift() 取出第一个元素后,会从数组中删掉
p = p.then(...chain.shift());
}
return p;
为什么中间的发送请求在最后才回复结果
发送请求是在请求拦截器2,请求拦截器1后执行,其中发送请求中的then是在执行链的最后才执行,也就是p之后
最终执行的结果是p.then,所以请求成功的提示信息是在最后才显示。get中的then异步请求需要在第二轮轮询机制中才执行。