axios 从入门到源码分析

HTTP 相关

MDN文档

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Overview

HTTP请求交互的基本过程

1579109143498

  1. 前后应用从浏览器端向服务器发送 HTTP 请求(请求报文)
  2. 后台服务器接收到请求后, 调度服务器应用处理请求, 向浏览器端返回 HTTP
    响应(响应报文)
  3. 浏览器端接收到响应, 解析显示响应体/调用监视回调

HTTP请求报文

  1. 请求行:

    method url
    GET /product_detail?id=2
    POST /login

  2. 多个请求头
    Host: www.baidu.com
    Cookie: BAIDUID=AD3B0FA706E; BIDUPSID=AD3B0FA706;
    Content-Type: application/x-www-form-urlencoded 或者 application/json

  3. 请求体
    username=tom&pwd=123
    {“username”: “tom”, “pwd”: 123}

HTTP响应报文

  1. 响应状态行: status statusText
  2. 多个响应头
    Content-Type: text/html;charset=utf-8
    Set-Cookie: BD_CK_SAM=1;path=/
  3. 响应体
    html 文本/json 文本/js/css/图片…

post 请求体参数格式

  1. Content-Type: application/x-www-form-urlencoded;charset=utf-8
    用于键值对参数,参数的键值用=连接, 参数之间用&连接
    例如: name=%E5%B0%8F%E6%98%8E&age=12
  2. Content-Type: application/json;charset=utf-8
    用于 json 字符串参数
    例如: {“name”: “%E5%B0%8F%E6%98%8E”, “age”: 12}
  3. Content-Type: multipart/form-data

常见的响应状态码

200 OK 请求成功。一般用于 GET 与 POST 请求
201 Created 已创建。成功请求并创建了新的资源
401 Unauthorized 未授权/请求要求用户的身份认证
404 Not Found 服务器无法根据客户端的请求找到资源
500 Internal Server Error 服务器内部错误,无法完成请求

不同类型的请求及其作用

  1. GET: 从服务器端读取数据
  2. POST: 向服务器端添加新数据
  3. PUT: 更新服务器端已经数据
  4. DELETE: 删除服务器端数据

API 的分类

  1. REST API: restful
    (1) 发送请求进行 CRUD 哪个操作由请求方式来决定
    (2) 同一个请求路径可以进行多个操作
    (3) 请求方式会用到 GET/POST/PUT/DELETE
  2. 非 REST API: restless
    (1) 请求方式不决定请求的 CRUD 操作
    (2) 一个请求路径只对应一个操作
    (3) 一般只有 GET/POST

使用 json-server 搭建 REST API

json-server 是什么?

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

使用 json-server

  1. 在线文档: https://github.com/typicode/json-server

  2. 下载: npm install -g json-server

  3. 目标根目录下创建数据库 json 文件: db.json

    1
    2
    3
    4
    5
    {
    "posts": [{ "id": 1, "title": "json-server", "author": "typicode" }],
    "comments": [{ "id": 1, "body": "some comment", "postId": 1 }],
    "profile": { "name": "typicode" }
    }
  4. 启动服务器执行命令: json-server --watch db.json

使用浏览器访问测试

http://localhost:3000/posts
http://localhost:3000/posts/1

使用 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
<script src="https://cdn.bootcss.com/axios/0.19.0/axios.js"></script>
<script>
// 30分钟内不再发预检请求
// axios.defaults.headers["Access-Control-Max-Age"] = "1800"

/* 1. GET请求: 从服务器端获取数据*/
function testGet() {
// axios.get('http://localhost:3000/posts') // 获取所有posts的数组
// axios.get('http://localhost:3000/posts/1') // 获取id为1的数组
// axios.get('http://localhost:3000/posts?id=1&id=2') // 获取id为1或2的数组
// axios.get('http://localhost:3000/posts?title=json-server&author=typicode')
}
testGet()

/* 2. POST请求: 向服务器端添加新数据*/
function testPost() {
// axios.post('http://localhost:3000/comments', {body: 'xxx', postId: 1}) // 保存数据
}
testPost()

/* 3. PUT请求: 更新服务器端已经数据 */
function testPut() {
// axios.put('http://localhost:3000/comments/4', {body: 'yyy', postId: 1})
}
testPut()

/* 4. DELETE请求: 删除服务器端数据 */
function testDelete() {
// axios.delete('http://localhost:3000/comments/4')
}
testDelete()
</script>

XHR 的理解和使用

MDN 文档

https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest

理解

  1. 使用 XMLHttpRequest (XHR)对象可以与服务器交互, 也就是发送 ajax 请求
  2. 前端可以获取到数据,而无需让整个的页面刷新。
  3. 这使得 Web 页面可以只更新页面的局部,而不影响用户的操作。

区别一般 http 请求与 ajax 请求

  1. ajax 请求是一种特别的 http 请求
  2. 对服务器端来说, 没有任何区别, 区别在浏览器端
  3. 浏览器端发请求: 只有 XHR 或 fetch 发出的才是 ajax 请求, 其它所有的都是
    非 ajax 请求
  4. 浏览器端接收到响应
    (1) 一般请求: 浏览器一般会直接显示响应体数据, 也就是我们常说的刷新/
    跳转页面
    (2) ajax 请求: 浏览器不会对界面进行任何更新操作, 只是调用监视的回调
    函数并传入响应相关数据

API

  1. XMLHttpRequest(): 创建 XHR 对象的构造函数

  2. status: 响应状态码值, 比如 200, 404

  3. statusText: 响应状态文本

  4. readyState: 标识请求状态的只读属性
    0: 初始
    1: open()之后
    2: send()之后
    3: 请求中
    4: 请求完成

  5. onreadystatechange: 绑定 readyState 改变的监听

  6. responseType: 指定响应数据类型, 如果是’json’, 得到响应后自动解析响应体数据

  7. response: 响应体数据, 类型取决于 responseType 的指定

  8. timeout: 指定请求超时时间, 默认为 0 代表没有限制

  9. ontimeout: 绑定超时的监听

  10. onerror: 绑定请求网络错误的监听

  11. open(): 初始化一个请求, 参数为: (method, url[, async])

  12. send(data): 发送请求

  13. abort(): 中断请求

  14. getResponseHeader(name): 获取指定名称的响应头值

  15. getAllResponseHeaders(): 获取所有响应头组成的字符串

  16. setRequestHeader(name, value): 设置请求头

XHR 的 ajax 封装(简单版 axios)

特点

  1. 函数的返回值为 promise, 成功的结果为 response, 异常的结果为 error

  2. 能处理多种类型的请求: GET/POST/PUT/DELETE

  3. 函数的参数为一个配置对象

    1
    2
    3
    4
    5
    6
    {
    url: '', // 请求地址
    method: '', // 请求方式 GET/POST/PUT/DELETE
    params: {}, // GET/DELETE 请求的 query 参数
    data: {}, // POST 或 DELETE 请求的请求体参数
    }
  4. 响应 json 数据自动解析为 js

编码实现

示例代码:https://github.com/cheungww/http-ajax-axios/blob/master/test/02_xhr.html

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
/* 
1.函数的返回值为promise, 成功的结果为response, 失败的结果为error
2.能处理多种类型的请求: GET/POST/PUT/DELETE
3.函数的参数为一个配置对象
{
url: '', // 请求地址
method: '', // 请求方式GET/POST/PUT/DELETE
params: {}, // GET/DELETE请求的query参数
data: {}, // POST或DELETE请求的请求体参数
}
4.响应json数据自动解析为js的对象/数组
*/
function axios({
url,
method = 'GET',
params = {},
data = {}
}) {
// 返回一个promise对象
return new Promise((resolve, reject) => {

// 处理method(转大写)
method = method.toUpperCase()

// 处理query参数(拼接到url上) id=1&xxx=abc
/*
{
id: 1,
xxx: 'abc'
}
*/
let queryString = ''
Object.keys(params).forEach(key => {
queryString += `${key}=${params[key]}&`
})
if (queryString) { // id=1&xxx=abc&
// 去除最后的&
queryString = queryString.substring(0, queryString.length - 1)
// 接到url
url += '?' + queryString
}


// 1. 执行异步ajax请求
// 创建xhr对象
const request = new XMLHttpRequest()
// 打开连接(初始化请求, 没有请求)
request.open(method, url, true)

// 发送请求
if (method === 'GET' || method === 'DELETE') {
request.send()
} else if (method === 'POST' || method === 'PUT') {
request.setRequestHeader('Content-Type', 'application/json;charset=utf-8') // 告诉服务器请求体的格式是json
request.send(JSON.stringify(data)) // 发送json格式请求体参数
}

// 绑定状态改变的监听
request.onreadystatechange = function () {
// 如果请求没有完成, 直接结束
if (request.readyState !== 4) {
return
}
// 如果响应状态码在[200, 300)之间代表成功, 否则失败
const { status, statusText } = request
// 2.1. 如果请求成功了, 调用resolve()
if (status >= 200 && status <= 299) {
// 准备结果数据对象response
const response = {
data: JSON.parse(request.response),
status,
statusText
}
resolve(response)
} else { // 2.2. 如果请求失败了, 调用reject()
reject(new Error('request error status is ' + status))
}
}
})
}

使用测试

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
/* 1. GET请求: 从服务器端获取数据*/
function testGet() {
axios({
// url: 'http://localhost:3000/posts',
url: 'http://localhost:3000/posts2',
method: 'GET',
params: {
id: 1,
xxx: 'abc'
}
}).then(
response => {
console.log(response)
},
error => {
alert(error.message)
}
)
}

/* 2. POST请求: 服务器端保存数据*/
function testPost() {
axios({
url: 'http://localhost:3000/posts',
method: 'POST',
data: {
"title": "json-server---",
"author": "typicode---"
}
}).then(
response => {
console.log(response)
},
error => {
alert(error.message)
}
)
}

/* 3. PUT请求: 服务器端更新数据*/
function testPut() {
axios({
url: 'http://localhost:3000/posts/1',
method: 'put',
data: {
"title": "json-server+++",
"author": "typicode+++"
}
}).then(
response => {
console.log(response)
},
error => {
alert(error.message)
}
)
}

/* 2. DELETE请求: 服务器端删除数据*/
function testDelete() {
axios({
url: 'http://localhost:3000/posts/2',
method: 'delete'
}).then(
response => {
console.log(response)
},
error => {
alert(error.message)
}
)
}

axios 的理解和使用

axios 是什么?

  1. 前端最流行的 ajax 请求库
  2. react/vue 官方都推荐使用 axios 发 ajax 请求
  3. 文档: https://github.com/axios/axios

axios 特点

  1. 基本 promise 的异步 ajax 请求库
  2. 浏览器端/node 端都可以使用
  3. 支持请求/响应拦截器
  4. 支持请求取消
  5. 请求/响应数据转换
  6. 批量发送多个请求

axios 常用语法

axios(config): 通用/最本质的发任意类型请求的方式

axios(url[, config]): 可以只指定 url 发 get 请求

axios.request(config): 等同于 axios(config)

axios.get(url[, config]): 发 get 请求

axios.delete(url[, config]): 发 delete 请求

axios.post(url[, data, config]): 发 post 请求

axios.put(url[, data, config]): 发 put 请求

axios.defaults.xxx: 请求的默认全局配置

axios.interceptors.request.use(): 添加请求拦截器

axios.interceptors.response.use(): 添加响应拦截器

axios.create([config]): 创建一个新的 axios(它没有下面的功能)

axios.Cancel(): 用于创建取消请求的错误对象

axios.CancelToken(): 用于创建取消请求的 token 对象

axios.isCancel(): 是否是一个取消请求的错误

axios.all(promises): 用于批量执行多个异步请求

axios.spread(): 用来指定接收所有成功数据的回调函数的方法

难点语法的理解和使用

axios.create(config)

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

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

  3. 为什么要设计这个语法?
    (1) 需求: 项目中有部分接口需要的配置与另一部分接口需要的配置不太一
    样, 如何处理
    (2) 解决: 创建 2 个新 axios, 每个都有自己特有的配置, 分别应用到不同要
    求的接口请求中

拦截器函数/ajax 请求/请求的回调函数的调用顺序

  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
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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>axios的处理链流程</title>
</head>

<body>

<script src="https://cdn.bootcss.com/axios/0.19.0/axios.js"></script>
<script>
/*
requestInterceptors: [{fulfilled1(){}, rejected1(){}}, {fulfilled2(){}, rejected2(){}}]
responseInterceptors: [{fulfilled11(){}, rejected11(){}}, {fulfilled22(){}, rejected22(){}}]
chain: [
fulfilled2, rejected2, fulfilled1, rejected1,
dispatchReqeust, undefined,
fulfilled11, rejected11, fulfilled22, rejected22

]
promise链回调: config
=> (fulfilled2, rejected2) => (fulfilled1, rejected1) // 请求拦截器处理
=> (dispatchReqeust, undefined) // 发请求
=> (fulfilled11, rejected11) => (fulfilled22, rejected22) // 响应拦截器处理
=> (onResolved, onRejected) // axios发请求回调处理
*/

// 添加请求拦截器(回调函数)
axios.interceptors.request.use(
config => {
console.log('request interceptor1 onResolved()')
return config
},
error => {
console.log('request interceptor1 onRejected()')
return Promise.reject(error);
}
)
axios.interceptors.request.use(
config => {
console.log('request interceptor2 onResolved()')
return config
},
error => {
console.log('request interceptor2 onRejected()')
return Promise.reject(error);
}
)
// 添加响应拦截器
axios.interceptors.response.use(
response => {
console.log('response interceptor1 onResolved()')
return response
},
function (error) {
console.log('response interceptor1 onRejected()')
return Promise.reject(error);
}
)
axios.interceptors.response.use(
response => {
console.log('response interceptor2 onResolved()')
return response
},
function (error) {
console.log('response interceptor2 onRejected()')
return Promise.reject(error);
}
)

axios.get('http://localhost:3000/posts')
.then(response => {
console.log('data', response.data)
})
.catch(error => {
console.log('error', error.message)
})

/* axios.get('http://localhost:/posts2')
.then(response => {
console.log('data', response.data)
})
.catch(error => {
console.log('error', error.message)
})
*/
</script>
</body>

</html>

取消请求

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

  2. 实现功能

    1. 点击按钮, 取消某个正在请求中的请求

      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
      <!DOCTYPE html>
      <html lang="en">

      <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>取消请求</title>
      </head>

      <body>
      <button onclick="getProducts1()">获取商品列表1</button><br>
      <button onclick="getProducts2()">获取商品列表2</button><br>
      <button onclick="cancelReq()">取消请求</button><br>

      <script src="https://cdn.bootcss.com/axios/0.19.0/axios.js"></script>
      <script>
      let cancel // 用于保存取消请求的函数
      function getProducts1() {
      axios({
      url: 'http://localhost:4000/products1',
      cancelToken: new axios.CancelToken((c) => { // c是用于取消当前请求的函数
      // 保存取消函数, 用于之后可能需要取消当前请求
      cancel = c
      })
      }).then(
      response => {
      cancel = null
      console.log('请求1成功了', response.data)
      },
      error => {
      cancel = null
      console.log('请求1失败了', error.message, error)
      }
      )
      }

      function getProducts2() {
      axios({
      url: 'http://localhost:4000/products2'
      }).then(
      response => {
      console.log('请求2成功了', response.data)
      },
      error => {
      cancel = null
      console.log('请求2失败了', error.message)
      }
      )
      }

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

      </html>
    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
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      <!DOCTYPE html>
      <html lang="en">

      <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>取消请求</title>
      </head>

      <body>
      <button onclick="getProducts1()">获取商品列表1</button><br>
      <button onclick="getProducts2()">获取商品列表2</button><br>
      <button onclick="cancelReq()">取消请求</button><br>

      <script src="https://cdn.bootcss.com/axios/0.19.0/axios.js"></script>
      <script>
      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('没有可取消的请求')
      }
      }
      </script>
      </body>

      </html>

      用了请求/响应拦截器

      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
      <!DOCTYPE html>
      <html lang="en">

      <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>取消请求</title>
      </head>

      <body>
      <button onclick="getProducts1()">获取商品列表1</button><br>
      <button onclick="getProducts2()">获取商品列表2</button><br>
      <button onclick="cancelReq()">取消请求</button><br>

      <script src="https://cdn.bootcss.com/axios/0.19.0/axios.js"></script>
      <script>

      // 添加请求拦截器
      axios.interceptors.request.use((config) => {
      // 在准备发请求前, 取消未完成的请求
      if (typeof cancel === 'function') {
      cancel('取消请求')
      }
      // 添加一个cancelToken的配置
      config.cancelToken = new axios.CancelToken((c) => { // c是用于取消当前请求的函数
      // 保存取消函数, 用于之后可能需要取消当前请求
      cancel = c
      })

      return config
      })

      // 添加响应拦截器
      axios.interceptors.response.use(
      response => {
      cancel = null
      return response
      },
      error => {
      if (axios.isCancel(error)) {// 取消请求的错误
      // cancel = null
      console.log('请求取消的错误', error.message) // 做相应处理
      // 中断promise链接
      return new Promise(() => { })
      } else { // 请求出错了
      cancel = null
      // 将错误向下传递
      // throw error
      return Promise.reject(error)
      }
      }
      )


      let cancel // 用于保存取消请求的函数
      function getProducts1() {
      axios({
      url: 'http://localhost:4000/products1',
      }).then(
      response => {
      console.log('请求1成功了', response.data)
      },
      error => {// 只用处理请求失败的情况, 取消请求的错误的不用
      console.log('请求1失败了', error.message)
      }
      )
      }

      function getProducts2() {

      axios({
      url: 'http://localhost:4000/products2',
      }).then(
      response => {
      console.log('请求2成功了', response.data)
      },
      error => {
      console.log('请求2失败了', error.message)
      }
      )
      }

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

      </html>

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 # 入口文件

官方库:https://github.com/axios/axios

含中文注释的axios库:https://github.com/cheungww/axios

源码分析

axios 与 Axios 的关系?

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

instance 与 axios 的区别?

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

axios 运行的整体流程

1579110979771

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

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

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

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

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

1579111115553

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

  2. 响应拦截器

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

axios 的请求/响应数据转换器是什么?
  1. 请求转换器: 对请求头和请求体数据进行特定处理的函数
1
2
3
4
if (utils.isObject(data)) {
setContentTypeIfUnset(headers,'application/json;charset=utf-8');
return JSON.stringify(data);
}
  1. 响应转换器: 将响应体 json 字符串解析为 js 对象或数组的函数
    response.data = JSON.parse(response.data)
response 的整体结构
1
2
3
{
data, status, statusText, headers, config, request
}
error 的整体结构
1
2
3
{
message,response, request,
}
如何取消未完成的请求?
  1. 当配置了 cancelToken 对象时, 保存 cancel 函数
    (1) 创建一个用于将来中断请求的 cancelPromise
    (2) 并定义了一个用于取消请求的 cancel 函数
    (3) 将 cancel 函数传递出来
  2. 调用 cancel()取消请求
    (1) 执行 cacel 函数, 传入错误信息 message
    (2) 内部会让 cancelPromise 变为成功, 且成功的值为一个 Cancel 对象
    (3) 在 cancelPromise 的成功回调中中断请求, 并让发请求的 proimse 失败, 失败的 reason 为 Cacel 对象
-------文章到此结束  感谢您的阅读-------