小白必看:前端競(jìng)態(tài)條件的產(chǎn)生與解決
一、什么是異步請(qǐng)求的競(jìng)態(tài)問(wèn)題
二、如何解決異步請(qǐng)求的競(jìng)態(tài)問(wèn)題
2.1 交互層面解決
2.2 取消請(qǐng)求
2.3 拋棄無(wú)用的請(qǐng)求
參考資料
一、什么是異步請(qǐng)求的競(jìng)態(tài)問(wèn)題
首先,先闡述一下什么是競(jìng)態(tài)問(wèn)題,現(xiàn)在我有一個(gè)前端頁(yè)面如下如圖,它的功能是根據(jù)用戶的查詢條件來(lái)請(qǐng)求和展示列表數(shù)據(jù)。
網(wǎng)絡(luò)正常的情況下我們直接請(qǐng)求并展示數(shù)據(jù)就可以了,完全沒(méi)有技術(shù)的難度。但是當(dāng)網(wǎng)絡(luò)不穩(wěn)定時(shí)就會(huì)出現(xiàn)查詢條件和頁(yè)面展示結(jié)果不一致的情況。我們舉例說(shuō)明:
首先用戶在描述輸入框輸入“快樂(lè)”,然后點(diǎn)擊查詢 , 這次我們稱為第一次請(qǐng)求
緊接著用戶在描述輸入框輸入“悲傷”,然后點(diǎn)擊查詢,這次我們稱為第二次請(qǐng)求
網(wǎng)絡(luò)波動(dòng)時(shí),如果第二次請(qǐng)求的結(jié)果比第一次請(qǐng)求先返回,頁(yè)面上描述輸入框展示的是 “悲傷”,但是頁(yè)面展示的列表數(shù)據(jù)卻是第一次請(qǐng)求查詢出的“快樂(lè)”對(duì)應(yīng)的結(jié)果。
二、如何解決異步請(qǐng)求的競(jìng)態(tài)問(wèn)題
這里我整理出解決這個(gè)問(wèn)題的幾種方法供大家參考(下文的代碼主要用來(lái)展示思路,并未經(jīng)過(guò)測(cè)試)。
2.1 交互層面解決
在發(fā)起請(qǐng)求后,我們添加全局的 loading 遮罩,或者 禁用****查詢按鈕 ,這樣的話,我們?cè)谝粋€(gè)請(qǐng)求未完成前不能發(fā)送新的請(qǐng)求,這樣就能解決了。
但是這個(gè)方法有幾個(gè)缺點(diǎn):
阻斷交互
觸發(fā)查詢的動(dòng)作很多樣,如回車鍵等。這種情況下需要考慮的點(diǎn)會(huì)比較多
要說(shuō)服產(chǎn)品、交互的同事(如果你特別能 Battle 需求,就忽略這一條)
2.2 取消請(qǐng)求
如果我們能夠在每次請(qǐng)求時(shí),都先取消上一次的請(qǐng)求就能確保最終的查詢結(jié)果和查詢的條件是一致的。
2.2.1 axios
我們以 axios 的 cancellation 舉例:
const CancelToken = axios.CancelToken;
let source
// 請(qǐng)求的函數(shù)
funtion query (keyword) {
if (source) {
source.cancel('取消請(qǐng)求');
}
source = CancelToken.source();
return axios.post('/list', {
keyword
}, {
cancelToken: source.token
}).catch(function (thrown) {
// 區(qū)別處理取消請(qǐng)求和請(qǐng)求錯(cuò)誤
if (axios.isCancel(thrown)) {
// 取消請(qǐng)求的邏輯
} else {
// 請(qǐng)求錯(cuò)誤
}
});
}
上面的代碼中,在每次查詢前都使用 source.cancel() 取消了上一次的請(qǐng)求。
2.2.2 可取消的 Promise
當(dāng)然,不是每個(gè)人都會(huì)使用 axios 作為請(qǐng)求庫(kù),一個(gè)通用的做法是定制一個(gè)可取消的 Promise 來(lái)封裝請(qǐng)求。(注意:Promise 是不能取消的,這里取消指的是手動(dòng)把 Promise 設(shè)為 rejected 狀態(tài) ),代碼如下:
let doCancel
// 請(qǐng)求的函數(shù)
funtion query (keyword) {
if (doCancel) {
// 設(shè)置上一次的 Promise 設(shè)為 rejected 狀態(tài)
doCancel('取消請(qǐng)求');
}
return new Promise(function(resolve, reject) {
// 掛載 reject 方法
doCancel = reject
const xhr = new XMLHttpRequest();
xhr.on("load", resolve);
xhr.on("error", reject);
xhr.open("POST", '/list', true);
// 發(fā)送請(qǐng)求條件,這里未作處理
xhr.send(null);
}).catch(function (thrown) {
// 區(qū)別處理取消請(qǐng)求和請(qǐng)求錯(cuò)誤
if (axios.isCancel(thrown)) {
// 取消請(qǐng)求的邏輯
} else {
// 請(qǐng)求錯(cuò)誤
}
});
}
如果你不想折騰,這里推薦使用 bluebird 的 cancellation 功能
2.3 拋棄無(wú)用的請(qǐng)求
最后一種處理方式最為比較容易理解:只處理當(dāng)前查詢條件對(duì)應(yīng)請(qǐng)求結(jié)果,其它的查詢條件的結(jié)果我們都認(rèn)為是無(wú)用的請(qǐng)求,對(duì)于無(wú)用的請(qǐng)求我們?cè)诨卣{(diào)函數(shù)里不處理就可以了。
// 請(qǐng)求標(biāo)記
let gobalReqID = 0
// 請(qǐng)求的函數(shù)
funtion query (keyword) {
gobalReqID++
let curReqID = gobalReqID
return axios.post('/list', {
keyword
}).then(res => {
// 對(duì)比閉包內(nèi)的 curReqID 是否和 gobalReqID 一致
if (gobalReqID === curReqID) {
return res
} else {
return Promse.reject('無(wú)用的請(qǐng)求')
}
})
}
上面的代碼是使用一個(gè)自增的 reqID 和 閉包特性來(lái)判斷是否是無(wú)用的請(qǐng)求的,對(duì)于比較簡(jiǎn)單的查詢條件,我們可以直接判斷查詢條件的是否一致即可。
參考資料
作者:有朝 鏈接:https://juejin.cn/post/6970710521104302110
作者:有朝
歡迎關(guān)注微信公眾號(hào) :前端陽(yáng)光