axios 入门和源码分析
http 介绍
概述
HTTP 是一种能够获取如 HTML 这样的网络资源的 protocol(通讯协议)。它是在 Web 上进行数据交换的基础,是一种 client-server 协议,也就是说,请求通常是由像浏览器这样的接受方发起的。一个完整的 Web 文档通常是由不同的子文档拼接而成的,像是文本、布局描述、图片、视频、脚本等等。
客户端和服务端通过交换各自的消息(与数据流正好相反)进行交互。由像浏览器这样的客户端发出的消息叫做 requests,被服务端响应的消息叫做 responses。
HTTP 被设计于 20 世纪 90 年代初期,是一种可扩展的协议。它是应用层的协议,通过TCP,或者是TLS-加密的 TCP 连接来发送,理论上任何可靠的传输协议都可以使用。因为其良好的扩展性,时至今日,它不仅被用来传输超文本文档,还用来传输图片、视频或者向服务器发送如 HTML 表单这样的信息。HTTP 还可以根据网页需求,仅获取部分 Web 文档内容更新网页。
详细内容见 HTTP 概述
HTTP 请求方法
HTTP 定义了一组请求方法, 以表明要对给定资源执行的操作。指示针对给定资源要执行的期望动作. 虽然他们也可以是名词, 但这些请求方法有时被称为 HTTP 动词. 每一个请求方法都实现了不同的语义, 但一些共同的特征由一组共享:: 例如一个请求方法可以是 safe, idempotent, 或 cacheable.
HTTP 请求头
HTTP 消息头允许客户端和服务器通过 request和 response传递附加信息。一个请求头由名称(不区分大小写)后跟一个冒号“:”,冒号后跟具体的值(不带换行符)组成。该值前面的引导空白会被忽略。
常用请求头:Accept 、Access-Control-Allow-Origin 、Connection 、Content-Length 、Content-Type 、Cookie 、Host 、Origin 、User-Agent 。
HTTP 请求体
http 请求体通常用于 POST 请求中,包括但不限于提交表单等操作。
格式为在请求头之后空一行填写,例如:
POST /test HTTP/1.1
Host: foo.example
Content-Type: application/x-www-form-urlencoded
Content-Length: 27
field1=value1&field2=value2
HTTP 响应代码
HTTP 响应状态代码指示特定 HTTP 请求是否已成功完成。响应分为五类:信息响应(100
–199
),成功响应(200
–299
),重定向(300
–399
),客户端错误(400
–499
)和服务器错误 (500
–599
)。状态代码由 section 10 of RFC 2616定义。
详见:HTTP 响应代码
XHR 的理解和使用
MDN 文档
理解
- 使用 XMLHttpRequest (XHR)对象可以与服务器交互, 也就是发送 ajax 请求。
- 前端可以获取到数据,而无需让整个的页面刷新。
- 这使得 Web 页面可以只更新页面的局部,而不影响用户的操作。
- xhr 和 fetch 都是 JavaScript 的底层 API,互不相关。
区别一般 http 请求与 ajax 请求
-
ajax 请求是一种特别的 http 请求。
-
对服务器端来说,没有任何区别,区别在浏览器端。
-
浏览器端发请求:只有 XHR 或 fetch 发出的才是 ajax 请求,其它所有的都是 非 ajax 请求。
-
浏览器端接收到响应
(1) 一般请求:浏览器一般会直接显示响应体数据,也就是我们常说的刷新 / 跳转页面。
(2) ajax 请求:浏览器不会对界面进行任何更新操作,只是调用监视的回调 函数并传入响应相关数据。
API
构造函数
-
XMLHttpRequest()
该构造函数用于初始化一个 XMLHttpRequest 实例对象。在调用下列任何其他方法之前,必须先调用该构造函数,或通过其他方式,得到一个实例对象。
属性
此接口继承了 XMLHttpRequestEventTarget 和 EventTarget 的属性。
-
XMLHttpRequest.onreadystatechange
当 readyState 属性发生变化时,调用的 EventHandler (en-US)。
-
XMLHttpRequest.readyState 只读
返回 一个无符号短整型(unsigned short)数字,代表请求的状态码。
-
XMLHttpRequest.response 只读
返回一个 ArrayBuffer、Blob、Document,或 DOMString,具体是哪种类型取决于 XMLHttpRequest.responseType 的值。其中包含整个响应实体(response entity body)。
-
XMLHttpRequest.responseText 只读
返回一个 DOMString,该 DOMString 包含对请求的响应,如果请求未成功或尚未发送,则返回 null。
-
XMLHttpRequest.responseType
一个用于定义响应类型的枚举值(enumerated value)。
-
XMLHttpRequest.responseURL 只读
返回经过序列化(serialized)的响应 URL,如果该 URL 为空,则返回空字符串。
-
XMLHttpRequest.responseXML 只读
返回一个 Document,其中包含该请求的响应,如果请求未成功、尚未发送或时不能被解析为 XML 或 HTML,则返回 null。
-
XMLHttpRequest.status 只读
返回一个无符号短整型(unsigned short)数字,代表请求的响应状态。
-
XMLHttpRequest.statusText 只读
返回一个 DOMString,其中包含 HTTP 服务器返回的响应状态。与 XMLHTTPRequest.status 不同的是,它包含完整的响应状态文本(例如,“200 OK”)。
注意:根据 HTTP/2 规范(8.1.2.4 Response Pseudo-Header Fields,响应伪标头字段),HTTP/2 没有定义任何用于携带 HTTP/1.1 状态行中包含的版本(version)或者原因短语(reason phrase)的方法。
-
XMLHttpRequest.timeout
一个无符号长整型(unsigned long)数字,表示该请求的最大请求时间(毫秒),若超出该时间,请求会自动终止。
-
XMLHttpRequestEventTarget.ontimeout
当请求超时调用的 EventHandler。
-
XMLHttpRequest.upload 只读
XMLHttpRequestUpload,代表上传进度。
-
XMLHttpRequest.withCredentials
一个布尔值,用来指定跨域 Access-Control 请求是否应当带有授权信息,如 cookie 或授权 header 头。
非标准属性
- 事件处理器
作为 XMLHttpRequest 实例的属性之一,所有浏览器都支持 onreadystatechange。
后来,许多浏览器实现了一些额外的事件(onload、onerror、onprogress 等)。
更多现代浏览器,包括 Firefox,除了可以设置 on* 属性外,也提供标准的监听器 addEventListener() API 来监听 XMLHttpRequest 事件。
方法
-
XMLHttpRequest.abort()
如果请求已被发出,则立刻中止请求。
-
XMLHttpRequest.getAllResponseHeaders()
以字符串的形式返回所有用 CRLF 分隔的响应头,如果没有收到响应,则返回 null。
-
XMLHttpRequest.getResponseHeader()
返回包含指定响应头的字符串,如果响应尚未收到或响应中不存在该报头,则返回 null。
-
XMLHttpRequest.open()
初始化一个请求。该方法只能在 JavaScript 代码中使用,若要在 native code 中初始化请求,请使用 openRequest()。
-
XMLHttpRequest.overrideMimeType()
覆写由服务器返回的 MIME 类型。
-
XMLHttpRequest.send()
发送请求。如果请求是异步的(默认),那么该方法将在请求发送后立即返回。
-
XMLHttpRequest.setRequestHeader() 设置 HTTP 请求头的值。必须在 open() 之后、send() 之前调用 setRequestHeader() 方法。
事件
-
abort
当 request 被停止时触发,例如当程序调用 XMLHttpRequest.abort() 时。
-
error
当 request 遭遇错误时触发。
-
load
XMLHttpRequest 请求成功完成时触发。
-
loadend
当请求结束时触发, 无论请求成功 ( load) 还是失败 (abort 或 error)。
-
loadstart
接收到响应数据时触发。
-
progress
当请求接收到更多数据时,周期性地触发。
-
timeout
在预设时间内没有接收到响应时触发。
XHR 的 Ajax 封装(简单版 axios)
/**
* 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
let queryString = '';
Object.entries(params).forEach((item) => {
queryString += `${item[0]}=${item[1]}&`;
});
// 拼接url
url += queryString === '' ? queryString.substring(0, queryString.length - 1) : '';
// 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));
}
};
});
}
axios 的理解和使用
axios 是什么
- 前端最流行的 ajax 请求库
- react/vue 官方都推荐使用 axios 发 ajax 请求
- 开源地址
.axios 特点
- 基本 promise 的异步 ajax 请求库
- 浏览器端/node 端都可以使用
- 支持请求/响应拦截器
- 支持请求取消
- 请求/响应数据转换
- 批量发送多个请求
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.create(config)
-
根据指定配置创建一个新的 axios, 也就就每个新 axios 都有自己的配置
-
新 axios 只是没有取消请求和批量发请求的方法, 其它所有语法都是一致的
-
为什么要设计这个语法?
(1) 需求: 项目中有部分接口需要的配置与另一部分接口需要的配置不太一 样, 如何处理
(2) 解决: 创建 2 个新 axios, 每个都有自己特有的配置, 分别应用到不同要 求的接口请求中
拦截器函数/ajax 请求/请求的回调函数的调用顺序
- 说明: 调用 axios()并不是立即发送 ajax 请求, 而是需要经历一个较长的流程
- 流程: 请求拦截器 2 => 请求拦截器 1 => 发 ajax 请求 => 响应拦截器 1 => 响应拦截器 2 => 请求的回调
- 注意: 此流程是通过 promise 串连起来的, 请求拦截器传递的是 config, 响应拦截器传递的是 response
取消请求
-
基本流程
配置 cancelToken 对象
缓存用于取消请求的 cancel 函数
在后面特定时机调用 cancel 函数取消请求
在错误回调中判断如果 error 是 cancel, 做相应处理
-
实现功能
点击按钮, 取消某个正在请求中的请求;在请求一个接口前, 取消前面一个未完成的请求
axios 源码分析
axios 与 Axios 的关系?
- 从语法上来说: axios 不是 Axios 的实例
- 从功能上来说: axios 是 Axios 的实例
- axios 是 Axios.prototype.request 函数 bind()返回的函数
- axios 作为对象有 Axios 原型对象上的所有方法, 有 Axios
instance 与 axios 的区别?
-
相同:
(1) 都是一个能发任意请求的函数: request(config)
(2) 都有发特定请求的各种方法: get()/post()/put()/delete()
(3) 都有默认配置和拦截器的属性: defaults/interceptors
-
不同:
(1) 默认匹配的值很可能不一样
(2) instance 没有 axios 后面添加的一些方法: create()/CancelToken()/all()
axios 运行的整体流程
-
整体流程:
request(config) ==> dispatchRequest(config) ==> xhrAdapter(config)
-
request(config):
将请求拦截器 / dispatchRequest() / 响应拦截器 通过 promise 链串连起来, 返回 promise
-
dispatchRequest(config):
转换请求数据 ==> 调用 xhrAdapter()发请求 ==> 请求返回后转换响应数据。返回 promis
-
xhrAdapter(config):
创建 XHR 对象, 根据 config 进行相应设置, 发送特定请求, 并接收响应数据, 返回 promise
axios 的请求/响应拦截器是什么?
-
请求拦截器:
在真正发送请求前执行的回调函数
可以对请求进行检查或配置进行特定处理
成功的回调函数, 传递的默认是 config(也必须是)
失败的回调函数, 传递的默认是 error
-
响应拦截器
在请求得到响应后执行的回调函数
可以对响应数据进行特定处理
成功的回调函数, 传递的默认是 response
失败的回调函数, 传递的默认是 erro
axios 的请求/响应数据转换器是什么?
-
请求转换器: 对请求头和请求体数据进行特定处理的函数
if (utils.isObject(data)) { setContentTypeIfUnset(headers, 'application/json;charset=utf-8'); return JSON.stringify(data); }
-
响应转换器: 将响应体 json 字符串解析为 js 对象或数组的函数
response.data = JSON.parse(response.data);
response 的整体结构
{
data,
status,
statusText,
headers,
config,
request,
}
error 的整体结构
{
message,
response,
request,
}
如何取消未完成的请求?
-
当配置了 cancelToken 对象时, 保存 cancel 函数
(1) 创建一个用于将来中断请求的 Promise 对象: cancelPromise
(2) 并定义了一个用于取消请求的 resolved 函数: cancel
(3) 将 cancel 函数传递出来
-
调用 cancel()取消请求
(1) 执行 cacel 函数, 传入错误信息 message
(2) 内部会让 cancelPromise 变为成功, 且成功的值为一个 Cancel 对象
(3) 在 cancelPromise 的成功回调中中断请求, 并让发请求的 proimse 失败, 失败的 reason 为 Cacel 对象