一、XHR 的相关内容
-
MDN
文档 :https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest
- 理解
XHR
,如下所示:
- 使用
XMLHttpRequest (XHR)
对象可以与服务器交互, 也就是发送ajax
请求 - 前端可以获取到数据,而无需让整个的页面刷新。
- 这使得
Web
页面可以只更新页面的局部,而不影响用户的操作。
- 区别
ajax
请求与一般HTTP
请求,如下所示:
-
ajax
请求是一种特别的http
请求: - 只有通过
XHR/fetch
发送的是ajax
请求, 其它都是一般HTTP
请求 - 对服务器端来说, 没有任何区别, 区别在浏览器端
浏览器端发请求: - 只有
XHR
或fetch
发出的才是ajax
请求, 其它所有的都是非ajax
请求 - 浏览器端接收到响应,如下:
- 一般请求: 浏览器一般会直接显示响应体数据, 也就是我们常说的刷新/跳转页面
-
ajax
请求: 浏览器不会对界面进行任何更新操作, 只是调用监视的回调函数并传入响应相关数据
-
API
,如下所示:
-
XMLHttpRequest()
: 创建XHR
对象的构造函数 -
status
: 响应状态码值, 比如200, 404
-
statusText
: 响应状态文本 readyState
: 标识请求状态的只读属性
-
0
: 初始 -
1
:open()
之后 -
2
:send()
之后 -
3
: 请求中 -
4
: 请求完成
-
onreadystatechange
: 绑定readyState
改变的监听 -
responseType
: 指定响应数据类型, 如果是'json'
, 得到响应后自动解析响应体数据 -
response
: 响应体数据, 类型取决于responseType
的指定 -
timeout
: 指定请求超时时间, 默认为0
代表没有限制 -
ontimeout
: 绑定超时的监听 -
onerror
: 绑定请求网络错误的监听 -
open()
: 初始化一个请求, 参数为:(method, url[, async])
-
send(data)
: 发送请求 -
abort()
: 中断请求 -
getResponseHeader(name)
: 获取指定名称的响应头值 -
getAllResponseHeaders()
: 获取所有响应头组成的字符串 -
setRequestHeader(name, value)
: 设置请求头
- 使用
XHR
封装一个发ajax
请求的通用函数,如下所示:
- 函数的返回值为
promise
, 成功的结果为response
, 异常的结果为error
- 能处理多种类型的请求:
GET/POST/PUT/DELETE
- 函数的参数为一个配置对象,
url/method/params/data
,如下:
{
url: '', // 请求地址
method: '', // 请求方式 GET/POST/PUT/DELETE
params: {}, // GET/DELETE 请求的 query 参数
data: {}, // POST 或 DELETE 请求的请求体参数
}
- 响应
json
数据自动解析为了js
- 使用
XHR
封装一个发ajax
请求的通用函数,代码如下所示:
<!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>使用XHR封装ajax请求参数</title>
</head>
<body>
<button onclick="testGet()">发送GET请求</button><br>
<button onclick="testPost()">发送POST请求</button><br>
<button onclick="testPut()">发送PUT请求</button><br>
<button onclick="testDelete()">发送Delete请求</button><br>
<script>
/* 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)
}
)
}
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.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))
}
}
})
}
</script>
</body>
</html>
二、axios 的理解和使用
-
axios
是什么,如下所示:
- 前端最流行的
ajax
请求库 -
react/vue
官方都推荐使用axios
发ajax
请求 - 文档:
https://github.com/axios/axios
-
axios
的特点,如下所示:
- 基于
promise
的封装XHR
的异步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.spread()
: 用来指定接收所有成功数据的回调函数的方法
-
axios
的基本使用,代码如下所示:
<!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>
<div>
<button onclick="testGet()">GET请求</button>
<button onclick="testPost()">POST请求</button>
<button onclick="testPut()">PUT请求</button>
<button onclick="testDelete()">DELETE请求</button>
</div>
<script src="https://cdn.bootcss.com/axios/0.19.0/axios.js"></script>
<script>
// 指定默认配置
axios.defaults.baseURL = 'http://localhost:3000'
/* 1. GET请求: 从服务器端获取数据*/
function testGet() {
// axios.get('/posts?id=1')
axios({
url: '/posts',
params: {
id: 1
}
})
.then(response => {
console.log('/posts get', response.data)
})
}
/* 2. POST请求: 向服务器端添加新数据*/
function testPost() {
// axios.post('/posts', {"title": "json-server3", "author": "typicode3"})
axios({
url: '/posts',
method: 'post',
data: {"title": "json-server4", "author": "typicode4"}
})
.then(response => {
console.log('/posts post', response.data)
})
}
/* 3. PUT请求: 更新服务器端已经数据 */
function testPut() {
// axios.put('http://localhost:3000/posts/4', {"title": "json-server...", "author": "typicode..."})
axios({
url: '/posts/4',
method: 'put',
data: {"title": "json-server5", "author": "typicode5"}
})
.then(response => {
console.log('/posts put', response.data)
})
}
/* 4. DELETE请求: 删除服务器端数据 */
function testDelete() {
// axios.delete('http://localhost:3000/posts/4')
axios({
url: '/posts/5',
method: 'delete'
})
.then(response => {
console.log('/posts delete', response.data)
})
}
</script>
</body>
</html>
-
axios.create(config)
,如下:
- 根据指定配置创建一个新的
axios
, 也就就每个新axios
都有自己的配置 - 新
axios
只是没有取消请求和批量发请求的方法, 其它所有语法都是一致的 - 为什么要设计这个语法? 如下所示:
- 需求: 项目中有部分接口需要的配置与另一部分接口需要的配置不太一样, 如何处理
- 解决: 创建两个新
axios
, 每个都有自己特有的配置, 分别应用到不同要 求的接口请求中
-
axios.create()
,代码如下所示:
<!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.create()</title>
</head>
<body>
<script src="https://cdn.bootcss.com/axios/0.19.0/axios.js"></script>
<script>
axios.defaults.baseURL = 'http://localhost:3000'
// 使用axios发请求
axios({
url: '/posts' // 请求3000
})
// axios({
// url: '/xxx' // 请求4000
// })
const instance = axios.create({
baseURL: 'http://localhost:4000'
})
// 使用instance发请求
// instance({
// url: '/xxx' // 请求4000
// })
instance.get('/xxx')
</script>
</body>
</html>
- 拦截器函数/
ajax
请求/请求的回调函数的调用顺序,如下所示:
- 说明: 调用
axios()
并不是立即发送ajax
请求, 而是需要经历一个较长的流程 - 流程:
请求拦截器2 => 请求拦截器 1 => 发ajax请求 => 响应拦截器1 => 响 应拦截器 2 => 请求的回调
- 注意: 此流程是通过
promise
串连起来的, 请求拦截器传递的是config
, 响应 拦截器传递的是response
-
axios
的处理链流程,代码如下所示:
<!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>
// 添加请求拦截器(回调函数)
axios.interceptors.request.use(
config => {
console.log('request interceptor1 onResolved()') // 2 后
return config
},
error => {
console.log('request interceptor1 onRejected()')
return Promise.reject(error);
}
)
axios.interceptors.request.use(
config => {
console.log('request interceptor2 onResolved()') // 1 先
return config
},
error => {
console.log('request interceptor2 onRejected()')
return Promise.reject(error);
}
)
// 添加响应拦截器
axios.interceptors.response.use(
response => {
console.log('response interceptor1 onResolved()') // 3 先
return response
},
function (error) {
console.log('response interceptor1 onRejected()')
return Promise.reject(error);
}
)
axios.interceptors.response.use(
response => {
console.log('response interceptor2 onResolved()') // 4 后
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>
最后的处理结果,request interceptor2 是第一个请求,request interceptor1 是第二个请求,response interceptor1 是第一个响应,response interceptor2 是第二个响应。
- 取消请求,如下所示:
- 基本流程,如下:
- 配置
cancelToken
对象 - 缓存用于取消请求的
cancel
函数 - 在后面特定时机调用
cancel
函数取消请求 - 在错误回调中判断如果
error
是cancel
, 做相应处理
- 实现功能,如下:
- 点击按钮, 取消某个正在请求中的请求 在请求一个接口前,
- 取消前面一个未完成的请求
- 对于
axios
取消请求的,代码如下所示:
- 第一个:
<!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>