一、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>