一、axios介绍

json-server介绍

github: https://github.com/typicode/json-server

1
npm install -g json-server

用来快速搭建 REST API 的工具包

axios

github: https://github.com/axios/axios

运行在浏览器和node.js,用于发送Ajax请求

1
2
3
4
axios.get('http://localhost:3000/posts?title=json-server&author=typicode');
axios.post('http://localhost:3000/posts', {title: 'xxx', author: 'yyyy'}) // 保存数据
axios.put('http://localhost:3000/comments/2', {body: 'yyy', postI d: 2})
axios.delete('http://localhost:3000/comments/2')
  1. 基本 promise 的异步 ajax 请求库

  2. 浏览器端/node 端都可以使用

  3. 支持请求/响应拦截器

  4. 支持请求取消

  5. 请求/响应数据转换

  6. 批量发送多个请求

axios配置对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
{
url: '/user', //url

method: 'get', // 请求类型

baseURL: 'https://some-domain.com/api/', //基础url

//对请求参数和响应结果做预处理
transformRequest: [function (data, headers) {
return data;
}],

transformResponse: [function (data) {
return data;
}],

//请求头信息
headers: {'X-Requested-With': 'XMLHttpRequest'},

// 设定url参数
params: {
ID: 12345
},

// 参数序列化,用的少
paramsSerializer: function (params) {
return Qs.stringify(params, {arrayFormat: 'brackets'})
},

// 请求体设置,json和参数
data: {
firstName: 'Fred'
},

data: 'Country=Brasil&City=Belo Horizonte',

// 超时时间
timeout: 1000, // default is `0` (no timeout)

//跨域请求时,是否携带cookie
withCredentials: false, // default

// 请求适配器
adapter: function (config) {
/* ... */
},

// 对请求基础
auth: {
username: 'janedoe',
password: 's00pers3cret'
},

responseType: 'json', // default

responseEncoding: 'utf8', // default

//胯域请求标识
xsrfCookieName: 'XSRF-TOKEN', // default

xsrfHeaderName: 'X-XSRF-TOKEN', // default

// 上传下载的回调
onUploadProgress: function (progressEvent) {
// Do whatever you want with the native progress event
},

onDownloadProgress: function (progressEvent) {
// Do whatever you want with the native progress event
},

// http响应体最大尺寸
maxContentLength: 2000,
// http请求体最大尺寸
maxBodyLength: 2000,

// 对响应结果的成功进行设置
validateStatus: function (status) {
return status >= 200 && status < 300; // default
},

//最大跳转
maxRedirects: 5, // default

// socket连接,用于向docker守护进程发送信息
socketPath: null, // default

// 用户请求
httpAgent: new http.Agent({ keepAlive: true }),
httpsAgent: new https.Agent({ keepAlive: true }),

// 代理请求,node.js
proxy: {
protocol: 'https',
host: '127.0.0.1',
port: 9000,
auth: {
username: 'mikeymike',
password: 'rapunz3l'
}
},

// 用于取消ajax请求
cancelToken: new CancelToken(function (cancel) {
}),

// 解压缩
decompress: true // default
}
}

axios创建实例对象

  1. 根据指定配置创建一个新的 axios, 也就就每个新 axios 都有自己的配置

  2. 新 axios 只是没有取消请求和批量发请求的方法, 其它所有语法都是一致的

  3. 为什么要设计这个语法?

    (1) 需求: 项目中有部分接口需要的配置与另一部分接口需要的配置不太一 样, 如何处理

    (2) 解决: 创建 2 个新 axios, 每个都有自己特有的配置, 分别应用到不同要 求的接口请求中

1
2
3
4
5
6
7
8
9
const request = axios.create({
baseURL: "http://api.apiopen.top",
timeout: 2000
})
request({
url:'/getJoke',
}).then(response => {
console.log(request)
})

axios拦截器

  1. 说明: 调用 axios()并不是立即发送 ajax 请求, 而是需要经历一个较长的流程

  2. 流程: 请求拦截器2 => 请求拦截器 1 => 发ajax请求 => 响应拦截器1 => 响应拦截器 2 => 请求的回调

  3. 注意: 此流程是通过 promise 串连起来的, 请求拦截器传递的是config, 响应 拦截器传递的是 response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 请求拦截器
axios.interceptors.request.use(function (config) {
// 例如给请求头添加token
return config;
}, function (error) { //请求拦截器失败
// Do something with request error
return Promise.reject(error);
});

// 设置响应拦截器
axios.interceptors.response.use(function (response) {
// 判断请求服务器返回数据
return response;
}, function (error) {
// 判断请求失败原因
return Promise.reject(error);
});

取消请求

  1. 基本流程

    配置 cancelToken 对象
    缓存用于取消请求的 cancel 函数
    在后面特定时机调用 cancel 函数取消请求
    在错误回调中判断如果 error 是 cancel, 做相应处理

  2. 实现功能

    点击按钮, 取消某个正在请求中的请求
    在请求一个接口前, 取消前面一个未完成的请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
let cancel  // 用于保存取消请求的函数
function getProducts1() {
// 在准备发请求前, 取消未完成的请求
if (typeof cancel==='function') {
cancel('取消请求')
}

axios({
url: 'http://localhost:4000/products1',
cancelToken: new axios.CancelToken((c) => { // c是用于取消当前请求的函数
// 保存取消函数, 用于之后可能需要取消当前请求
cancel = c
})
}).then(
response => {
cancel = null
console.log('请求1成功了', response.data)
},
error => {

if (axios.isCancel(error)) {
// cancel = null
console.log('请求1取消的错误', error.message)
} else { // 请求出错了
cancel = null
console.log('请求1失败了', error.message)
}

}
)
}

function getProducts2() {
// 在准备发请求前, 取消未完成的请求
if (typeof cancel==='function') {
cancel('取消请求')
}

axios({
url: 'http://localhost:4000/products2',
cancelToken: new axios.CancelToken((c) => { // c是用于取消当前请求的函数
// 保存取消函数, 用于之后可能需要取消当前请求
cancel = c
})
}).then(
response => {
cancel = null
console.log('请求2成功了', response.data)
},
error => {
if (axios.isCancel(error)) {
// cancel = null
console.log('请求2取消的错误', error.message)
} else { // 请求出错了
cancel = null
console.log('请求2失败了', error.message)
}
}
)
}

function cancelReq() {
// alert('取消请求')
// 执行取消请求的函数
if (typeof cancel === 'function') {
cancel('强制取消请求')
} else {
console.log('没有可取消的请求')
}
}

二、axios源码

文件目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
├── /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/ # 一些辅助方法
│ ├── axios.js # 对外暴露接口
│ ├── defaults.js # axios 的默认配置
│ └── utils.js # 公用工具
├── package.json # 项目信息
├── index.d.ts # 配置 TypeScript 的声明文件
└── index.js

create流程

axios的创建流程

通过createInstance()函数将传入默认配置创建一个axios对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
'use strict';

var utils = require('./utils');
var bind = require('./helpers/bind');
var Axios = require('./core/Axios');
var mergeConfig = require('./core/mergeConfig');
var defaults = require('./defaults');

/**
* Create an instance of Axios
*
* @param {Object} defaultConfig The default config for the instance
* @return {Axios} A new instance of Axios
*/
function createInstance(defaultConfig) {
/*
创建Axios的实例
原型对象上有一些用来发请求的方法: get()/post()/put()/delete()/request()
自身上有2个重要属性: defaults/interceptors
*/
var context = new Axios(defaultConfig);

// axios和axios.create()对应的就是request函数
// Axios.prototype.request.bind(context)
var instance = bind(Axios.prototype.request, context); // axios

// 将Axios原型对象上的方法拷贝到instance上: request()/get()/post()/put()/delete()
utils.extend(instance, Axios.prototype, context);

// 将Axios实例对象上的属性拷贝到instance上: defaults和interceptors属性
utils.extend(instance, context);

return instance;
}

// Create the default instance to be exported
//通过配置创建axios对象
var axios = createInstance(defaults);

// Expose Axios class to allow class inheritance
// axios 添加Axios属性,属性值为构造函数对象
axios.Axios = Axios;

// Factory for creating new instances
// 工厂函数,用于返回创建实例对象的函数
axios.create = function create(instanceConfig) {
return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

// Expose Cancel & CancelToken
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.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
'use strict';

var utils = require('./../utils');
var buildURL = require('../helpers/buildURL');
var InterceptorManager = require('./InterceptorManager');
var dispatchRequest = require('./dispatchRequest');
var mergeConfig = require('./mergeConfig');

/**
* Axios构造函数
* Create a new instance of Axios
* @param {Object} instanceConfig The default config for the instance
*/
function Axios(instanceConfig) {
// 将指定的config, 保存为defaults属性
this.defaults = instanceConfig;
// 将包含请求/响应拦截器管理器的对象保存为interceptors属性
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}

/**
* 用于发请求的函数
* 我们使用的axios就是此函数bind()返回的函数
*
* 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 = arguments[1] || {};
config.url = arguments[0];
} else {
config = config || {};
}

// 合并配置
config = mergeConfig(this.defaults, config);
// 添加method配置, 默认为get
config.method = config.method ? config.method.toLowerCase() : 'get';

/*
创建用于保存请求/响应拦截函数的数组
数组的中间放发送请求的函数
数组的左边放请求拦截器函数(成功/失败)
数组的右边放响应拦截器函数
*/
var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);

// 后添加的请求拦截器保存在数组的前面
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// 后添加的响应拦截器保存在数组的后面
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});


// 通过promise的then()串连起所有的请求拦截器/请求方法/响应拦截器
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}

// 返回用来指定我们的onResolved和onRejected的promise
return promise;
};

// 用来得到带query参数的url
Axios.prototype.getUri = function getUri(config) {
config = mergeConfig(this.defaults, config);
return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, '');
};

// Provide aliases for supported request methods
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;

模拟实现axios

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//构造函数
function Axios(config){
//初始化
this.default = config;
this.intercepters = {
request:{},
response:{}
}
}

//原型添加相关方法
Axios.prototype.request = function(config){
console.log('发送ajax请求')
}
Axios.prototype.get = function(config){
this.request({method:'get'});
}
Axios.prototype.post = function(config){
this.request({method:'post'});
}

//声明函数
function createInstance(config){
//实例化一个对象
let context = new Axios(conif); //context.get(),context.post() 不能当做函数使用
//创建请求对象函数
let instance = Axios.protoype.request.bind(context); //instance是一个函数,并且可以 instance({}) 此时instance不能instance.get()

//将Axios.prototype对象中的方法添加到instance实例对象中
Object.key(Axios.prototype).forEach(key => {
console.log(key);
instance[key] = Axios.prototype[key].bind(context);
})

//为instance函数对象添加 default 与 interceptors
Object.key(context).forEach(key => {
instance[key] = context[key]
})
}


let axios = createInstance();
axios.get({});

Axios发送请求过程详解

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//用于发送请求 
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);
// 添加method配置, 默认为get
config.method = config.method ? config.method.toLowerCase() : 'get';

/*
创建用于保存请求/响应拦截函数的数组
数组的中间放发送请求的函数
数组的左边放请求拦截器函数(成功/失败)
数组的右边放响应拦截器函数
*/
//创建拦截器中间件,第一个参数用于发送请求,第二个参数用于补位
var chain = [dispatchRequest, undefined];
//创建一个成功的promise,且成功的值为合并后的请求配置
var promise = Promise.resolve(config);

// 后添加的请求拦截器保存在数组的前面
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// 后添加的响应拦截器保存在数组的后面
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});


// 通过promise的then()串连起所有的请求拦截器/请求方法/响应拦截器
while (chain.length) {
//依次取出promise的回调函数并执行
promise = promise.then(chain.shift(), chain.shift());
}

// 返回用来指定我们的onResolved和onRejected的promise
return promise;
};

dispatchRequest.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
function throwIfCancellationRequested(config) {
if (config.cancelToken) {
config.cancelToken.throwIfRequested();
}
}

/**
* Dispatch a request to the server using the configured adapter.
*
* @param {object} config The config that is to be used for the request
* @returns {Promise} The Promise to be fulfilled
*/
module.exports = function dispatchRequest(config) {

/*
如果请求已经被取消, 直接抛出异常
*/
throwIfCancellationRequested(config);

/*
合并config中的baseURL和url
*/
if (config.baseURL && !isAbsoluteURL(config.url)) {
config.url = combineURLs(config.baseURL, config.url);
}

// Ensure headers exist
config.headers = config.headers || {};

/*
对config中的data进行必要的转换处理
设置相应的Content-Type请求头
*/
config.data = transformData(
config.data,
config.headers,
config.transformRequest
);

/*
整合config中所有的header
*/
config.headers = utils.merge(
config.headers.common || {},
config.headers[config.method] || {},
config.headers || {}
);

utils.forEach(
['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
function cleanHeaderConfig(method) {
delete config.headers[method];
}
);

var adapter = config.adapter || defaults.adapter;

return adapter(config).then(function onAdapterResolution(response) {
throwIfCancellationRequested(config);

/*
对response中还没有解析的data数据进行解析
json字符串解析为js对象/数组
*/
response.data = transformData(
response.data,
response.headers,
config.transformResponse
);

return response;
}, function onAdapterRejection(reason) {
if (!isCancel(reason)) {
throwIfCancellationRequested(config);

// Transform response data
if (reason && reason.response) {
reason.response.data = transformData(
reason.response.data,
reason.response.headers,
config.transformResponse
);
}
}

return Promise.reject(reason);
});
};

模拟实现axios发送请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//1.声明构造函数
function Axios(config) {
this.config = config;
}
Axios.prototype.request = function (config) {
//发送请求
//创建一个promise对象
let promise = Promise.resolve(config);
//声明一个数组
let chains = [dispatchRequest, undefined]; //undefined占位
//循环处理
//调用then方法指定回调
let result = promise.then(chains[0], chains[1]);
return result;
}
//2.dispatchRequest
function dispatchRequest(config) {
//调用适配器发送请求
return xhrAdapter(config).then(response => {
console.log(response);
//对响应结果做处理
return response;
}, error => {
console.log(error);
throw error;
})
}

//3.adapter
function xhrAdapter(config) {
console.log('xhrAdapter 函数执行');
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 && x.status < 300){
//成功的状态
resolve({
//配置
config:config,
//响应体
data: xhr.response,
//响应头,axios对其做了格式化
headers: xhr.getAllResponseHeaders(),
//xhr请求对象
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/post'
}).then(response => {
console.log(response);
})

拦截器

源码实现

use方法只是把回调保存在了request和response对象上的handlers上,当创建Axios对象时,把请求拦截器放在数组最前面,响应拦截器放在数组最后面,最终通过循环的方式以跳板的形式(一组一组的放,响应执行响应,请求执行请求)。

requset中

1
2
3
4
5
6
7
8
9
10
11
12
 // 后添加的请求拦截器保存在数组的前面
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// 后添加的响应拦截器保存在数组的后面
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
// 通过promise的then()串连起所有的请求拦截器/请求方法/响应拦截器
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}

InterceptorManager.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
function InterceptorManager() {
// 用来保存拦截器函数的数组, 数组中每个都是对象, 对象中包含fulfilled/rejected方法
this.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
*/
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
// 添加成功和失败的拦截器函数
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
// 返回拦截器对应的ID(也就是下标)
return this.handlers.length - 1;
};

/**
* Remove an interceptor from the stack
*
* @param {Number} id The ID that was returned by `use`
*/
InterceptorManager.prototype.eject = function eject(id) {
// 移除指定id对应的拦截器
if (this.handlers[id]) {
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
*/
InterceptorManager.prototype.forEach = function forEach(fn) {
// 遍历处理所有保存的拦截器
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
};

module.exports = InterceptorManager;

模拟实现拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
//1.声明构造函数
function Axios(config) {
this.config = config;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
}
}

//拦截器管理器的构造函数
function InterceptorManager() {
this.handlers = [];
}

InterceptorManager.prototype.use = function (fulfilled, rejected) {
this.handlers.push({
fulfilled,
rejected
})
}

Axios.prototype.request = function (config) {

//发送请求
//创建一个promise对象
let promise = Promise.resolve(config);
//声明一个数组
//undefined占位,因为dispatchRequest永远返回一个成功的promise
//保证有偶数个的方法,两两执行作为成功和失败的方法。
let chains = [dispatchRequest, undefined];


//处理拦截器
//将请求拦截器的回调压入chains的前面
this.interceptors.request.handlers.forEach(item => {
chains.unshift(item.fulfilled, item.rejected);
})
//将响应拦截器的回调压入chains的前面
this.interceptors.response.handlers.forEach(item => {
chains.push(item.fulfilled, item.rejected);
})
console.log(chains);
//循环处理
//调用then方法指定回调
while (chains.length) {
promise = promise.then(chains.shift(), chains.shift());
}
return promise;

//调用then方法指定回调
let result = promise.then(chains[0], chains[1]);
return result;

}

//2.dispatchRequest,简化了发送直接返回一个成功的promise对象
function dispatchRequest(config) {
//调用适配器发送请求
return new Promise((resolve, reject) => {
console.log("发送请求");
resolve({
status: 200,
statusText: 'OK'
})
})
}

//3.创建实例
let context = new Axios({});
//将context属性config interceptors添加至axios函数对象身上
//4.创建axios函数
let axios = Axios.prototype.request.bind(context);

Object.keys(context).forEach(key => {
axios[key] = context[key];
})

axios.interceptors.request.use(function (config) {
console.log("请求拦截器");
return config;
}, function (config) {
return cofnig;
})

axios.interceptors.response.use(function (success) {
console.log("响应拦截器");
return success;
}, function (error) {
return error;
})
/* console.dir(axios); */
axios({
method: 'GET',
url: 'http:localhost:3000/post'
}).then(response => {
console.log(response);
})

axios取消请求工作原理

源码

创建对象时有一个config:cancelToken这个函数的状态只要已发送改变,就会取消请求,axios将取消函数暴露出来,二在xhr中会判断是否有这个属性,如果这个promise状态改变之后,就会停止ajax的发送。

1
2
3
4
let cancel = c;
let cancelToken = new axios.CancelToken(function(){
cancel = c;
})

CancelToken.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
'use strict';

var Cancel = require('./Cancel');

/**
* 用于取消请求的对象构造函数
*
* A `CancelToken` is an object that can be used to request cancellation of an operation.
*
* @class
* @param {Function} executor The executor function.
*/
function CancelToken(executor) {
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.');
}

// 为取消请求准备一个promise对象, 并保存resolve函数
var resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
//将修改promise状态的函数暴露出去,会改变promise的状态,resolvePromise()
resolvePromise = resolve;
});

// 保存当前token对象
var token = this;

// 立即执行接收的执行器函数, 并传入用于取消请求的cancel函数
executor(function cancel(message) {
// 如果token中有reason了, 说明请求已取消
if (token.reason) {
// Cancellation has already been requested
return;
}
// 将token的reason指定为一个Cancel对象
token.reason = new Cancel(message);
// 将取消请求的promise指定为成功, 值为reason
resolvePromise(token.reason);
});
}

/**
* 如果请求已经被取消, 抛出reason也就是Cancel对象的异常
* Throws a `Cancel` if cancellation has been requested.
*/
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
if (this.reason) {
throw this.reason;
}
};

/**
* 创建一个包含token对象和cancel函数的对象, 并添加给CancelToken
*
* Returns an object that contains a new `CancelToken` and a function that, when called,
* cancels the `CancelToken`.
*/
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel
};
};

module.exports = CancelToken;

在xhr.js中有这样一段代码 xhr.about

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 如果配置了cancelToken
if (config.cancelToken) {
// 指定用于中断请求的回调函数,当promise状态改变之后会调用
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
// 中断请求
request.abort();
// 让请求的promise失败
reject(cancel);
// Clean up request
request = null;
});
}

模拟实现取消请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
//1.声明构造函数
function Axios(config) {
this.config = config;
}
Axios.prototype.request = function (config) {
//发送请求
//创建一个promise对象
let promise = Promise.resolve(config);
//声明一个数组
let chains = [dispatchRequest, undefined]; //undefined占位
//循环处理
//调用then方法指定回调
let result = promise.then(chains[0], chains[1]);
return dispatchRequest(config);
}
//2.dispatchRequest
function dispatchRequest(config) {
//调用适配器发送请求
return xhrAdapter(config);
}

//3.adapter
function xhrAdapter(config) {
console.log('xhrAdapter 函数执行');
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 && x.status < 300){
//成功的状态
resolve({
//配置
config:config,
//响应体
data: xhr.response,
//响应头,axios对其做了格式化
headers: xhr.getAllResponseHeaders(),
//xhr请求对象
request: xhr,
//响应状态码
status: xhr.status,
//响应状态字符串
statusText: xhr.statusText
});
}else{
//失败的状态
reject(new Error('请求失败,失败的状态码为'+xhr.status));
}
}
}
//关于取消请求
if(config.cancelToken){
//对cancelToken对象上的promise对象指定成功的回调
config.cancelToken.promise.then(value =>{
xhr.abort();
})
}
})
}

//4.创建axios函数
const context = new Axios({})
let axios = Axios.prototype.request.bind(context);
//CancelToken构造函数
function CancelToken(executor){
//声明一个变量
var resolvePormise;
this.promise = new Promise((resolve) => {
//将resolve赋值给resolvePormise
resolvePormise = resolve;
})
//调用executor函数,把执行的权限给executor函数
executor(function(){
//执行resolvePromise函数
resolvePormise();
})
}


let CancelToken = new CancelToken(function(c){
cancel = c;
});

axios({
method:'GET',
url:'http:localhost:3000/post'
}).then(response => {
console.log(response);
})

三、总结

1.axios 与 Axios 的关系?

  1. 从语法上来说: axios 不是 Axios 的实例
  2. 从功能上来说: axios 是 Axios 的实例
  3. axios 是 Axios.prototype.request 函数 bind()返回的函数
  4. axios 作为对象有 Axios 原型对象上的所有方法, 有 Axios 对象上所有属性

2.instance 与 axios 的区别?

  1. 相同:
    • 都是一个能发任意请求的函数: request(config)
    • 都有发特定请求的各种方法: get()/post()/put()/delete()
    • 都有默认配置和拦截器的属性: defaults/interceptors
  2. 不同:
    • 默认匹配的值很可能不一样
    • instance 没有 axios 后面添加的一些方法: create()/CancelToken()/all()

3.axios 运行的整体流程?

image-20210915181317823
  1. 整体流程: request(config) ==> dispatchRequest(config) ==> xhrAdapter(config)

  2. request(config): 将请求拦截器 / dispatchRequest() / 响应拦截器 通过 promise 链串连起来, 返回 promise

  3. dispatchRequest(config): 转换请求数据 ===> 调用 xhrAdapter()发请求 ===> 请求返回后转换响应数 据. 返回 promise

  4. xhrAdapter(config): 创建 XHR 对象, 根据 config 进行相应设置, 发送特定请求, 并接收响应数据, 返回 promise

4.axios 的请求/响应拦截器是什么?

image-20210915181528784

  1. 请求拦截器:

    • 在真正发送请求前执行的回调函数
    • 可以对请求进行检查或配置进行特定处理
    • 成功的回调函数, 传递的默认是 config(也必须是)
    • 失败的回调函数, 传递的默认是 error
  2. 响应拦截器

    • 在请求得到响应后执行的回调函数
    • 可以对响应数据进行特定处理
    • 成功的回调函数, 传递的默认是 response
    • 失败的回调函数, 传递的默认是 error

5.axios 的请求/响应数据转换器是什么?

  1. 请求转换器: 对请求头和请求体数据进行特定处理的函数

    1
    2
    3
    if (utils.isObject(data)) { 
    setContentTypeIfUnset(headers, 'application/json;charset=utf-8'); return JSON.stringify(data);
    }
  2. 响应转换器: 将响应体 json 字符串解析为 js 对象或数组的函数

    1
    response.data = JSON.parse(response.data)

6.response 的整体结构

1
{ data, status, statusText, headers, config, request }

7.error 的整体结构

1
{ message,response, request, }

8.如何取消未完成的请求?

  1. 当配置了 cancelToken 对象时, 保存 cancel 函数
    • 创建一个用于将来中断请求的 cancelPromise
    • 并定义了一个用于取消请求的 cancel 函数
    • 将 cancel 函数传递出来
  2. 调用 cancel()取消请求
    • 执行 cancel函数, 传入错误信息 message
    • 内部会让 cancelPromise 变为成功, 且成功的值为一个 Cancel 对象
    • 在 cancelPromise 的成功回调中中断请求, 并让发请求的 proimse 失败, 失败的 reason 为 Cancel对象