小白必看:前端競態(tài)條件的產生與解決
點擊上方?前端陽光,關注公眾號
回復加群,加入技術交流群交流群

一、什么是異步請求的競態(tài)問題
二、如何解決異步請求的競態(tài)問題
2.1 交互層面解決
2.2 取消請求
2.3 拋棄無用的請求
參考資料
一、什么是異步請求的競態(tài)問題
首先,先闡述一下什么是競態(tài)問題,現(xiàn)在我有一個前端頁面如下如圖,它的功能是根據用戶的查詢條件來請求和展示列表數(shù)據。

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

往期推薦
我組建了技術交流群,里面有很多?大佬,歡迎進來交流、學習、共建。回復?加群?即可。后臺回復「電子書」即可免費獲取?27本?精選的前端電子書!回復內推,可內推各廠內推碼
???“分享、點贊、在看” 支持一波??
