本文讲述:

  1. axios的创建过程
  2. axios如何安排请求
  3. axios拦截器实现原理

create axios

axios是怎么来的 ?

1、创建Axios构造函数

2、在Axios原型链上添加request方法

3、在Axios原型上添加基于request方法的get/post….等方法

4、创建createInstance方法

- 创建一个Axios的实例化,命名为content
- 创建一个instance指向request方法
- 将Axios的属性(config/interceptors)挂载到instance上
- 将Axios的方法(get/post)挂载到instance上

axios的创建过程是为了让axios既可以当作函数被执行,也可以直接调用其原型上的方法执行,axios本质上就是request函数,用于任务的编排和调度。

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
function Axios(config) {
this.config = config;
this.interceptors = {
request: {},
response: {}
}
}

Axios.prototype.request = function (config) {
console.log(`执行了 ${config.method} 方法`);
}

Axios.prototype.get = function (config) {
return this.request({ method: 'get' })
}

Axios.prototype.post = function (config) {
return this.request({ method: 'post' })
}

function createInstance(config) {
let context = new Axios(config);
let instance = Axios.prototype.request.bind(context); // 让 axios 支持以 axios({method: 'get'}) 的形式进行网络请求
Object.keys(Axios.prototype).forEach(key => {
instance[key] = Axios.prototype[key] // 让 axios 支持 axios.get() 的形式进行网络请求
})

Object.keys(context).forEach(key => {
instance[key] = context[key] // 将Axios上的属性挂载到实例上
})
return instance
}

let axios = createInstance({ method: 'get' })
// console.dir(axios);
axios.get(); // 执行了 get 方法
axios({ method: 'post' }); // 执行了 post 方法

axios request

本章主要讲述axios是如何完成网络请求的,图例是请求的过程和相关函数的作用:

axios请求过程

  1. axios()执行时,会调用Axios.prototype.request方法
  2. request方法首先是用用户传入配置生成一个Promise实例 Promise.resolve(config),这样就保证了任务链条的每个阶段都可以通过PromiseResult接收用户传入参数
  3. 其次,request方法实现了任务的编排,比如将请求拦截器、网络请求和响应拦截器通过Promise串联在一起,这样的好处是代码执行结果包括错误会通过链条层层传递,我们可以很清楚的掌握整个事件的PromiseStatus和PromiseResult。
  4. 任务编排执行的过程中,request方法会调用了dispatchRequest方法进行网络请求,这可以让我们不用考虑执行环境的差异性
  5. dispatchRequest会区分当前是浏览器环境还是Node环境,如果是浏览器环境,则调用xhrAdapter;否则调用httpAdapter进行网络请求。
  6. 当xhrAdapter/httpAdapter完成请求后,会将请求结果注入到了PromiseResult,在之后的链式调用过程中,我们就可以在任意环节通过PromiseResult获取请求数据
  7. 最后,Promise实例状态和结果通过链条层层传递,使得我们可以调用then方法进行接收。

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
// axios -> Axios.prototype.request -> dispatch -> xhrAdapter
// 创建Axios
function Axios(config) {
this.config = config;
}

// 构造任务编排链,包括请求拦截,发送请求,响应拦截
// 需要return一个Promise对象,支持axios().then()的调用方式
Axios.prototype.request = function (config) {
let chain = [dispatchRequest, undefined];
let promise = Promise.resolve(config); // 传入的config会随着promiseResult一层一层沿着任务调度往下传递
while (chain.length !== 0) {
promise = promise.then(chain.shift(), chain.shift())
}
return promise
}

// 需要返回一个promise对象,以支持request的链式调用
// 返回的promise对象是xhrAdapter执行的结果
function dispatchRequest(config) {
return new Promise((resolve, reject) => {
return xhrAdapter(config).then(res => {
resolve(res)
}, rea => { })
})
}

// 发起xhr请求,以Promise实例的形式返回结果
function xhrAdapter(config) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open(config.method, config.url);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
resolve(xhr.response)
}
}
})
}

let axios = Axios.prototype.request.bind(null);
axios({
url: 'http://localhost:6885/index.html',
method: 'get'
}).then(res=>{
console.log(res);
})

interceptors

  1. 拦截器是通过Axios实例下的this.interceptors构造出来的
  2. this.interceptors是个对象,包含两个属性request和response,它们的值都是基于InterceptorManager的工厂函数
  3. InterceptorManager工厂函数生产了两个handler数组,分别用来存放请求拦截器和响应拦截器
  4. InterceptorManager上提供了use方法,接收fullfilled/rejected两侧参数,参数类型为function
  5. 用户编写的拦截器函数会通过上面的步骤,压入request/response维护的handlers数组中
  6. 当axios执行的时候,在Axios.prototype.request方法中进行任务编排,逐步取出handlers数组中的函数并放置到chain链上
  7. 当所有方法取出完毕后,使用while循环迭代chain链,递进式完成所有任务,并以Promise的形式返回,若成功则返回成功的结果,若失败则返回失败的原因

响应拦截器过程

手写拦截器

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
// 拦截器interceptors -> Axios/this.interceptors -> request: new InterceptorManager() -> this.handler -> push(fullfilled,rejected) to handler -> push handler's item to Event chain 

function InterceptorManager() {
this.handler = [];
}

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

// 创建Axios
function Axios(config) {
this.config = config;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
}
}

// 构造任务编排链,包括请求拦截,发送请求,响应拦截
// 需要return一个Promise对象,支持axios().then()的调用方式
Axios.prototype.request = function (config) {
let chain = [dispatchRequest, undefined];
let promise = Promise.resolve(config); // 传入的config会随着promiseResult一层一层沿着任务调度往下传递

this.interceptors.request.handler.forEach(item => {
chain.unshift(item.fulfilled, item.rejected)
})
this.interceptors.response.handler.forEach(item => {
chain.push(item.fulfilled, item.rejected)
})

while (chain.length !== 0) {
promise = promise.then(chain.shift(), chain.shift())
}
return promise
}

// 需要返回一个promise对象,以支持request的链式调用
// 返回的promise对象是xhrAdapter执行的结果
function dispatchRequest(config) {
return new Promise((resolve, reject) => {
return xhrAdapter(config).then(res => {
resolve(res)
}, rea => { })
})
}

// 发起xhr请求,以Promise实例的形式返回结果
function xhrAdapter(config) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open(config.method, config.url);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
resolve(xhr.response)
}
}
})
}


function createInstance() {
let context = new Axios();
let instance = Axios.prototype.request.bind(context); // 让 axios 支持以 axios({method: 'get'}) 的形式进行网络请求
Object.keys(Axios.prototype).forEach(key => {
instance[key] = Axios.prototype[key] // 让 axios 支持 axios.get() 的形式进行网络请求
})

Object.keys(context).forEach(key => {
instance[key] = context[key] // 将Axios上的属性挂载到实例上
})
return instance
}




// ===========================================================
let axios = createInstance();
// 创建拦截器
axios.interceptors.request.use(config => {
config.data = 'hello world'
return config
}, err => { })
axios.interceptors.request.use(config => {
console.log('请求拦截器');
return config
}, err => { })

axios.interceptors.response.use(res => {
console.log('响应拦截器');
return res
}, err => { })



axios({
url: 'http://localhost:9137/index.html',
method: 'get'
}).then(res => {
console.log(res.slice(1, 10));
})