在我們?nèi)粘i_發(fā)中,有這樣一種場(chǎng)景必須要進(jìn)行處理,那就是在提交表單的時(shí)候,如果很快的重復(fù)點(diǎn)擊兩次,會(huì)造成重復(fù)請(qǐng)求,第二次請(qǐng)求就會(huì)報(bào)錯(cuò),給用戶帶來(lái)很不好的體驗(yàn),同時(shí)如果后端沒(méi)有加以控制,也容易造成數(shù)據(jù)重復(fù)。所以我們需要對(duì)這種重復(fù)請(qǐng)求進(jìn)行處理和控制。
axios是現(xiàn)在前端項(xiàng)目開發(fā)中必用的一個(gè)用于前端后網(wǎng)絡(luò)請(qǐng)求的工具,它是基于ajax和promise封裝而成,很受歡迎。
那么,今天就結(jié)合axios來(lái)實(shí)現(xiàn)一下如何取消重復(fù)請(qǐng)求。
思路整理
取消重復(fù)請(qǐng)求的思路就是,將每一次請(qǐng)求的url、method、params、data拼接起來(lái)組成一個(gè)key,然后添加到map中,下一次請(qǐng)求時(shí)就拿key在map中查找是否已存在,如果存在就表示重復(fù)請(qǐng)求,就取消,如果不存在就放行,等請(qǐng)求成功后在從map中刪除這個(gè)key。
axios中如何取消請(qǐng)求
- 普通請(qǐng)求取消
在普通的ajax中,是通過(guò)XMLHttpRequest的abort()方法實(shí)現(xiàn)取消請(qǐng)求的。
var ajax = new XMLHttpRequest();
ajax.open('GET', '/abc');
ajax.send();
setTimeout(() => {
ajax.abort(); // 取消當(dāng)前請(qǐng)求
}, 300);
- axios請(qǐng)求取消
axios中是通過(guò)CancelToken給每個(gè)請(qǐng)求添加一個(gè)cancelToken屬性,使得每個(gè)請(qǐng)求具備取消請(qǐng)求的能力。有兩種方法實(shí)現(xiàn):
// 方式一:
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token
}).then(res => {});
// cancel方法里面的參數(shù)是可選參數(shù)
source.cancel('取消請(qǐng)求');
// 方式二:
const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// 這里的c就是取消當(dāng)前請(qǐng)求的方法,這里把c賦值給cancel變量
cancel = c;
})
});
// 調(diào)用取消方法
cancel();
兩種實(shí)踐方式
- 重復(fù)請(qǐng)求取消前一次
- 封裝取消重復(fù)請(qǐng)求類
// cancel-request.js
import qs from 'qs'
export default class CancelRequest {
constructor() {
this.pendingRequest = new Map()
}
// 根據(jù)請(qǐng)求信息生成唯一標(biāo)識(shí)key
geterateReqKey(config) {
const { url, method, params, data } = config
return [url, method, qs.stringify(params), qs.stringify(data)].join('&')
}
// 把當(dāng)前請(qǐng)求信息添加到pendingRequest對(duì)象中
addPendingRequest(config, CancelToken) {
const requestKey = this.geterateReqKey(config)
config.cancelToken =
config.cancelToken ||
new CancelToken(cancel => {
if (!this.pendingRequest.has(requestKey)) {
// 把請(qǐng)求取消方法作為 map 值存起來(lái)
this.pendingRequest.set(requestKey, cancel)
}
})
}
// 檢查是否存在重復(fù)請(qǐng)求,若存在則取消前一次請(qǐng)求
removePendingRequest(config) {
const requestKey = this.geterateReqKey(config)
if (this.pendingRequest.has(requestKey)) {
const cancel = this.pendingRequest.get(requestKey)
// 取消請(qǐng)求
cancel(requestKey)
// 刪除map中對(duì)應(yīng)的屬性
this.removeRequestKey(config)
}
}
// 從pendingRequest中刪除對(duì)應(yīng)的key
removeRequestKey(config) {
const requestKey = this.geterateReqKey(config)
this.pendingRequest.delete(requestKey)
}
}
- 在
axios攔截中使用
// request.js
import axios from 'axios';
import CancelRequest from './cancel-request.js'
// 實(shí)例化
let cancelRequest = new CancelRequest()
const instance = axios.create({
// ...
});
// 請(qǐng)求攔截器
instance.interceptors.request.use(config => {
// 在請(qǐng)求開始之前檢查先前的請(qǐng)求,如果是重復(fù)請(qǐng)求,刪除之前的
cancelRequest.removePendingRequest(config);
// 如果不存在就將當(dāng)前請(qǐng)求添加到pendingRequest
cancelRequest.addPendingRequest(config);
return config;
}, err => {
Promise.reject(err);
});
// 響應(yīng)攔截器
instance.interceptors.response.use(res => {
// 移除成功請(qǐng)求記錄
cancelRequest.removeRequestKey(res.config)
return res.data;
}, err => {
// 失敗時(shí)也需要移除
cancelRequest.removeRequestKey(err.config || {} )
Promise.reject(err);
});
export default instance;
這種方式雖然是可以取消重復(fù)請(qǐng)求,但是瀏覽的network中取消的請(qǐng)求會(huì)顯示canceled狀態(tài),用戶是有感知的!于是,就有了第二種方式,取消第二次請(qǐng)求,并實(shí)現(xiàn)用戶無(wú)感。
- 重復(fù)請(qǐng)求取消第二次
- 封裝取消重復(fù)請(qǐng)求類
import qs from 'qs'
export default class CancelRequest {
constructor() {
this.pendingRequest = new Map()
}
// 根據(jù)請(qǐng)求信息生成唯一標(biāo)識(shí)key
geterateReqKey(config) {
const { url, method, params, data } = config
return [url, method, qs.stringify(params), qs.stringify(data)].join('&')
}
// 檢查是否是重復(fù)請(qǐng)求,如果是取消第二次
checkoutPendingRequest(config, CancelToken) {
// 為每個(gè)請(qǐng)求添加cancelToken,同時(shí)拿到source獲取到對(duì)每個(gè)請(qǐng)求取消請(qǐng)求的能力(cancel方法)
let source = null
if (config.cancelToken) {
source = config.source
} else {
source = CancelToken.source()
config.cancelToken = source.token
}
const requestKey = this.geterateReqKey(config)
if (this.pendingRequest.has(requestKey)) {
// 取消重復(fù)請(qǐng)求(第二次)
source.cancel('double request:' + requestKey)
} else {
// 沒(méi)重復(fù)就添加
this.pendingRequest.set(requestKey, source)
}
}
// 從請(qǐng)求列表中刪除
removeRequestKey(config) {
// 延遲一點(diǎn)是為了避免用戶快速多次點(diǎn)擊提交,而第一次請(qǐng)求成功立刻清除掉,第二次請(qǐng)求不會(huì)被取消
setTimeout(() => {
const requestKey = this.geterateReqKey(config)
this.pendingRequest.delete(requestKey)
}, 200)
}
}
- 在axios攔截中使用
// request.js
import axios from 'axios';
import CancelRequest from './cancel-request.js'
// 實(shí)例化
let cancelRequest = new CancelRequest()
const instance = axios.create({
// ...
});
// 請(qǐng)求攔截器
instance.interceptors.request.use(config => {
// 檢查之前是否存在相同的請(qǐng)求,如果存在則取消。不存在就記錄
cancelRequest.checkoutPendingRequest(config);
return config;
}, err => {
Promise.reject(err);
});
// 響應(yīng)攔截器
instance.interceptors.response.use(res => {
// 移除成功請(qǐng)求記錄
cancelRequest.removeRequestKey(res.config)
return res.data;
}, err => {
Promise.reject(err);
});
export default instance;
至此,就分享完畢,希望大家有所獲益!