日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
使用Axios攔截器解決「前端并發(fā)沖突」問題

背景

并發(fā)沖突問題, 是日常開發(fā)中一個比較常見的問題。

不同用戶在較短時間間隔內(nèi)變更數(shù)據(jù),或者某一個用戶進(jìn)行的重復(fù)提交操作都可能導(dǎo)致并發(fā)沖突。

并發(fā)場景在開發(fā)和測試階段難以排查全面,出現(xiàn)線上 bug 以后定位困難,因此做好并發(fā)控制是前后端開發(fā)過程中都需要重視的問題。

對于同一用戶短時間內(nèi)重復(fù)提交數(shù)據(jù)的問題,前端通??梢韵茸鲆粚訑r截。

本文將討論前端如何利用 axios 的攔截器過濾重復(fù)請求,解決并發(fā)沖突。

一般的處理方式 — 每次發(fā)請求添加 loading

在嘗試 axios 攔截器之前,先看看我們之前業(yè)務(wù)是怎么處理并發(fā)沖突問題的:

每次用戶操作頁面上的控件(輸入框、按鈕等),向后端發(fā)送請求的時候,都給頁面對應(yīng)的控件添加 loading 效果,提示正在進(jìn)行數(shù)據(jù)加載,同時也阻止 loading 效果結(jié)束前用戶繼續(xù)操作控件。

這是最直接有效的方式,如果你們前端團(tuán)隊(duì)成員足夠細(xì)心耐心,擁有良好的編碼習(xí)慣,這樣就可以解決大部分用戶不小心重復(fù)提交帶來的并發(fā)問題了。

更優(yōu)的解決方案:axios 攔截器統(tǒng)一處理

項(xiàng)目中需要前端限制并發(fā)的場景這么多,我們當(dāng)然要思考更優(yōu)更省事的方案。

既然是在每次發(fā)送請求的時候進(jìn)行并發(fā)控制,那如果能重新封裝下發(fā)請求的公共函數(shù),統(tǒng)一處理重復(fù)請求實(shí)現(xiàn)自動攔截,就可以大大簡化我們的業(yè)務(wù)代碼。

項(xiàng)目使用的 axios 庫來發(fā)送 http 請求,axios 官方為我們提供了豐富的 API,我們來看看攔截請求需要用到的兩個核心 API:

1. interceptors

攔截器包括請求攔截器和響應(yīng)攔截器,可以在請求發(fā)送前或者響應(yīng)后進(jìn)行攔截處理,用法如下:

 
 
 
 
  1. // 添加請求攔截器
  2. axios.interceptors.request.use(function (config) {
  3.   // 在發(fā)送請求之前做些什么
  4.   return config;
  5. }, function (error) {
  6.   // 對請求錯誤做些什么
  7.   return Promise.reject(error);
  8. });
  9. // 添加響應(yīng)攔截器
  10. axios.interceptors.response.use(function (response) {
  11.     // 對響應(yīng)數(shù)據(jù)做點(diǎn)什么
  12.     return response;
  13.   }, function (error) {
  14.     // 對響應(yīng)錯誤做點(diǎn)什么
  15.     return Promise.reject(error);
  16.   });

2. cancel token:

調(diào)用 cancel token API 可以取消請求。

官網(wǎng)提供了兩種方式來構(gòu)建 cancel token,我們采用這種方式:

通過傳遞一個 executor 函數(shù)到 CancelToken 的構(gòu)造函數(shù)來創(chuàng)建 cancel token,方便在上面的請求攔截器中檢測到重復(fù)請求可以立即執(zhí)行:

 
 
 
 
  1. const CancelToken = axios.CancelToken;
  2. let cancel;
  3. axios.get('/user/12345', {
  4.   cancelToken: new CancelToken(function executor(c) {
  5.     // executor 函數(shù)接收一個 cancel 函數(shù)作為參數(shù)
  6.     cancel = c;
  7.   })
  8. });
  9. // cancel the request
  10. cancel();

本文提供的思路就是利用 axios interceptors API 攔截請求,檢測是否有多個相同的請求同時處于 pending 狀態(tài),如果有就調(diào)用 cancel token API 取消重復(fù)的請求。

假如用戶重復(fù)點(diǎn)擊按鈕,先后提交了 A 和 B 這兩個完全相同(考慮請求路徑、方法、參數(shù))的請求,我們可以從以下幾種攔截方案中選擇其一:

  • 取消 A 請求,只發(fā)出 B 請求
  • 取消 B 請求,只發(fā)出 A 請求
  • 取消 B 請求,只發(fā)出 A 請求,把收到的 A 請求的返回結(jié)果也作為 B 請求的返回結(jié)果

第三種方案需要做監(jiān)聽處理增加了復(fù)雜性,結(jié)合我們實(shí)際的業(yè)務(wù)需求,最后采用了第二種方案來實(shí)現(xiàn),即:

只發(fā)第一個請求。在 A 請求還處于 pending 狀態(tài)時,后發(fā)的所有與 A 重復(fù)的請求都取消,實(shí)際只發(fā)出 A 請求,直到 A 請求結(jié)束(成功/失敗)才停止對這個請求的攔截。

具體實(shí)現(xiàn)

1.存儲所有 pending 狀態(tài)的請求

首先我們要將項(xiàng)目中所有的 pending 狀態(tài)的請求存儲在一個變量中,叫它 pendingRequests,

可以通過把 axios 封裝為一個單例模式的類,或者定義全局變量,來保證 pendingRequests變量在每次發(fā)送請求前都可以訪問,并檢查是否為重復(fù)的請求。

 
 
 
 
  1. let pendingRequests = new Map()

把每個請求的方法、url 和參數(shù)組合成一個字符串,作為標(biāo)識該請求的唯一 key,同時也是 pendingRequests 對象的 key:

 
 
 
 
  1. const requestKey = `${config.url}/${JSON.stringify(config.params)}/${JSON.stringify(config.data)}&request_type=${config.method}`;

幫助理解的小 tips:

  • 定義 pendingRequests 為 map 對象的目的是為了方便我們查詢它是否包含某個 key,以及添加和刪除 key。添加 key 時,對應(yīng)的 value 可以設(shè)置用戶自定義的一些功能參數(shù),后面擴(kuò)展功能的時候會用到。
  • config 是 axios 攔截器中的參數(shù),包含當(dāng)前請求的信息

2.在請求發(fā)出前檢查當(dāng)前請求是否重復(fù)

在請求攔截器中,生成上面的 requestKey,檢查 pendingRequests 對象中是否包含當(dāng)前請求的 requestKey

  • 有:說明是重復(fù)的請求,cancel 掉當(dāng)前請求
  • 沒有:把 requestKey 添加到 pendingRequests 對象中

因?yàn)楹竺娴捻憫?yīng)攔截器中還要用到當(dāng)前請求的 requestKey,為了避免踩坑,最好不要再次生成。

在這一步就把 requestKey 存回 axios 攔截器的 config 參數(shù)中,后面可以直接在響應(yīng)攔截器中通過 response.config.requestKey 取到。

代碼示例:

 
 
 
 
  1. // 請求攔截器
  2. axios.interceptors.request.use(
  3.   (config) => {
  4.     if (pendingRequests.has(requestKey)) {
  5.       config.cancelToken = new axios.CancelToken((cancel) => {
  6.         // cancel 函數(shù)的參數(shù)會作為 promise 的 error 被捕獲
  7.         cancel(`重復(fù)的請求被主動攔截: ${requestKey}`);
  8.       });
  9.     } else {
  10.       pendingRequests.set(requestKey, config);
  11.       config.requestKey = requestKey;
  12.     }
  13.     return config;
  14.   },
  15.   (error) => {
  16.     // 這里出現(xiàn)錯誤可能是網(wǎng)絡(luò)波動造成的,清空 pendingRequests 對象
  17.     pendingRequests.clear();
  18.     return Promise.reject(error);
  19.   }
  20. );

3.在請求返回后維護(hù) pendingRequests 對象

如果請求順利走到了響應(yīng)攔截器這一步,說明這個請求已經(jīng)結(jié)束了 pending 狀態(tài),那我們要把它從 pendingRequests 中除名:

 
 
 
 
  1. axios.interceptors.response.use((response) => {
  2.   const requestKey = response.config.requestKey;
  3.   pendingRequests.delete(requestKey);
  4.   return Promise.resolve(response);
  5. }, (error) => {
  6.   if (axios.isCancel(error)) {
  7.     console.warn(error);
  8.     return Promise.reject(error);
  9.   }
  10.   pendingRequests.clear();
  11.   return Promise.reject(error);
  12. })

4.需要清空 pendingRequests 對象的場景

遇到網(wǎng)絡(luò)波動或者超時等情況造成請求錯誤時,需要清空原來存儲的所有 pending 狀態(tài)的請求記錄,在上面演示的代碼已經(jīng)作了注釋說明。

此外,頁面切換時也需要清空之前緩存的 pendingRequests 對象,可以利用 Vue Router 的 beforeEach 鉤子:

 
 
 
 
  1. router.beforeEach((to, from, next) => {
  2.   request.clearRequestList();
  3.   next();
  4. });

功能擴(kuò)展

1.統(tǒng)一處理接口報(bào)錯提示

與后端約定好接口返回?cái)?shù)據(jù)的格式,對接口報(bào)錯的情況,可以統(tǒng)一在響應(yīng)攔截器中添加 toast 給用戶提示,

對于特殊的不需要報(bào)錯的接口,可以設(shè)置一個參數(shù)存入 axios 攔截器的 config 參數(shù)中,過濾掉報(bào)錯提示:

 
 
 
 
  1. // 接口返回 retcode 不為 0 時需要報(bào)錯,請求設(shè)置了 noError 為 true 則這個接口不報(bào)錯 
  2. if (
  3.   response.data.retcode &&
  4.   !response.config.noError
  5. ) {
  6.   if (response.data.message) {
  7.     Vue.prototype.$message({
  8.       showClose: true,
  9.       message: response.data.message,
  10.       type: 'error',
  11.     });
  12.   }
  13.   return Promise.reject(response.data);
  14. }

2.發(fā)送請求時給控件添加 loading 效果

上面利用 axios interceptors 過濾重復(fù)請求時,可以在控制臺拋出信息給開發(fā)者提示,在這個基礎(chǔ)上如果能給頁面上操作的控件添加 loading 效果就會對用戶更友好。

常見的 ui 組件庫都有提供 loading 服務(wù),可以指定頁面上需要添加 loading 效果的控件。下面是以 element UI 為例的示例代碼:

 
 
 
 
  1. // 給 loadingTarget 對應(yīng)的控件添加 loading 效果,儲存 loadingService 實(shí)例
  2. addLoading(config) {
  3.   if (!document.querySelector(config.loadingTarget)) return;
  4.   config.loadingService = Loading.service({
  5.     target: config.loadingTarget,
  6.   });
  7. }
  8. // 調(diào)用 loadingService 實(shí)例的 close 方法關(guān)閉對應(yīng)元素的 loading 效果
  9. closeLoading(config) {
  10.   config.loadingService && config.loadingService.close();
  11. }

與上面過濾報(bào)錯方式類似,發(fā)請求的時候?qū)⒃氐?class name 或 id 存入 axios 攔截器的 config 參數(shù)中,

在請求攔截器中調(diào)用 addLoading 方法, 響應(yīng)攔截器中調(diào)用 closeLoading 方法,就可以實(shí)現(xiàn)在請求 pending 過程中指定控件(如 button) loading,請求結(jié)束后控件自動取消 loading 效果。

支持多個攔截器組合使用

簡單看下 axios interceptors 部分實(shí)現(xiàn)源碼可以理解,它支持定義多個 interceptors,所以只要我們定義的 interceptors 符合 Promise.then 鏈?zhǔn)秸{(diào)用的規(guī)范,還可以添加更多功能:

 
 
 
 
  1. this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
  2.   chain.unshift(interceptor.fulfilled, interceptor.rejected);
  3. });
  4. this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
  5.   chain.push(interceptor.fulfilled, interceptor.rejected);
  6. });
  7. while (chain.length) {
  8.   promise = promise.then(chain.shift(), chain.shift());
  9. }

總結(jié)

并發(fā)問題很常見,處理起來又相對繁瑣,前端解決并發(fā)沖突時,可以利用 axios 攔截器統(tǒng)一處理重復(fù)請求,簡化業(yè)務(wù)代碼。

同時 axios 攔截器支持更多應(yīng)用,本文提供了部分常用擴(kuò)展功能的實(shí)現(xiàn),感興趣的同學(xué)可以繼續(xù)挖掘補(bǔ)充攔截器的其他用法。

今天的內(nèi)容就這么多,希望對你有幫助。


網(wǎng)站標(biāo)題:使用Axios攔截器解決「前端并發(fā)沖突」問題
文章網(wǎng)址:http://www.5511xx.com/article/dhosddj.html