<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【W(wǎng)eb技術(shù)】920- Axios 如何取消重復(fù)請(qǐng)求?

          共 7742字,需瀏覽 16分鐘

           ·

          2021-04-06 17:17

          在 Web 項(xiàng)目開發(fā)過程中,我們經(jīng)常會(huì)遇到重復(fù)請(qǐng)求的場(chǎng)景,如果系統(tǒng)不對(duì)重復(fù)的請(qǐng)求進(jìn)行處理,則可能會(huì)導(dǎo)致系統(tǒng)出現(xiàn)各種問題。比如重復(fù)的 post 請(qǐng)求可能會(huì)導(dǎo)致服務(wù)端產(chǎn)生兩筆記錄。那么重復(fù)請(qǐng)求是如何產(chǎn)生的呢?這里我們舉 2 個(gè)常見的場(chǎng)景:

          • 假設(shè)頁(yè)面中有一個(gè)按鈕,用戶點(diǎn)擊按鈕后會(huì)發(fā)起一個(gè) AJAX 請(qǐng)求。如果未對(duì)該按鈕進(jìn)行控制,當(dāng)用戶快速點(diǎn)擊按鈕時(shí),則會(huì)發(fā)出重復(fù)請(qǐng)求。
          • 假設(shè)在考試結(jié)果查詢頁(yè)面中,用戶可以根據(jù) “已通過”、“未通過” 和 “全部” 3 種查詢條件來查詢考試結(jié)果。如果請(qǐng)求的響應(yīng)比較慢,當(dāng)用戶在不同的查詢條件之前快速切換時(shí),就會(huì)產(chǎn)生重復(fù)請(qǐng)求。

          既然已經(jīng)知道重復(fù)請(qǐng)求是如何產(chǎn)生的,也知道了它會(huì)帶來一些問題。接下來,阿寶哥將以 Axios 為例,帶大家來一起解決重復(fù)請(qǐng)求的問題。

          一、如何取消請(qǐng)求

          Axios 是一個(gè)基于 Promise 的 HTTP 客戶端,同時(shí)支持瀏覽器和 Node.js 環(huán)境。它是一個(gè)優(yōu)秀的 HTTP 客戶端,被廣泛地應(yīng)用在大量的 Web 項(xiàng)目中。對(duì)于瀏覽器環(huán)境來說,Axios 底層是利用 XMLHttpRequest 對(duì)象來發(fā)起 HTTP 請(qǐng)求。如果要取消請(qǐng)求的話,我們可以通過調(diào)用 XMLHttpRequest 對(duì)象上的 abort 方法來取消請(qǐng)求:

          let xhr = new XMLHttpRequest();
          xhr.open("GET""https://developer.mozilla.org/"true);
          xhr.send();
          setTimeout(() => xhr.abort(), 300);

          而對(duì)于 Axios 來說,我們可以通過 Axios 內(nèi)部提供的 CancelToken 來取消請(qǐng)求:

          const CancelToken = axios.CancelToken;
          const source = CancelToken.source();

          axios.post('/user/12345', {
            name'semlinker'
          }, {
            cancelToken: source.token
          })

          source.cancel('Operation canceled by the user.'); // 取消請(qǐng)求,參數(shù)是可選的

          此外,你也可以通過調(diào)用 CancelToken 的構(gòu)造函數(shù)來創(chuàng)建 CancelToken,具體如下所示:

          const CancelToken = axios.CancelToken;
          let cancel;

          axios.get('/user/12345', {
            cancelTokennew CancelToken(function executor(c{
              cancel = c;
            })
          });

          cancel(); // 取消請(qǐng)求

          現(xiàn)在我們已經(jīng)知道在 Axios 中如何使用 CancelToken 來取消請(qǐng)求了,那么 CancelToken 內(nèi)部是如何工作的呢?這里我們先記住這個(gè)問題,后面阿寶哥將為你們揭開 CancelToken 背后的秘密。接下來,我們來分析一下如何判斷重復(fù)請(qǐng)求。

          二、如何判斷重復(fù)請(qǐng)求

          當(dāng)請(qǐng)求方式、請(qǐng)求 URL 地址和請(qǐng)求參數(shù)都一樣時(shí),我們就可以認(rèn)為請(qǐng)求是一樣的。因此在每次發(fā)起請(qǐng)求時(shí),我們就可以根據(jù)當(dāng)前請(qǐng)求的請(qǐng)求方式、請(qǐng)求 URL 地址和請(qǐng)求參數(shù)來生成一個(gè)唯一的 key,同時(shí)為每個(gè)請(qǐng)求創(chuàng)建一個(gè)專屬的 CancelToken,然后把 key 和 cancel 函數(shù)以鍵值對(duì)的形式保存到 Map 對(duì)象中,使用 Map 的好處是可以快速的判斷是否有重復(fù)的請(qǐng)求:

          import qs from 'qs'

          const pendingRequest = new Map();
          // GET -> params;POST -> data
          const requestKey = [method, url, qs.stringify(params), qs.stringify(data)].join('&'); 
          const cancelToken = new CancelToken(function executor(cancel{
            if(!pendingRequest.has(requestKey)){
              pendingRequest.set(requestKey, cancel);
            }
          })

          當(dāng)出現(xiàn)重復(fù)請(qǐng)求的時(shí)候,我們就可以使用 cancel 函數(shù)來取消前面已經(jīng)發(fā)出的請(qǐng)求,在取消請(qǐng)求之后,我們還需要把取消的請(qǐng)求從 pendingRequest 中移除。現(xiàn)在我們已經(jīng)知道如何取消請(qǐng)求和如何判斷重復(fù)請(qǐng)求,下面我們來介紹如何取消重復(fù)請(qǐng)求。

          三、如何取消重復(fù)請(qǐng)求

          因?yàn)槲覀冃枰獙?duì)所有的請(qǐng)求都進(jìn)行處理,所以我們可以考慮使用 Axios 的攔截器機(jī)制來實(shí)現(xiàn)取消重復(fù)請(qǐng)求的功能。Axios 為開發(fā)者提供了請(qǐng)求攔截器和響應(yīng)攔截器,它們的作用如下:

          • 請(qǐng)求攔截器:該類攔截器的作用是在請(qǐng)求發(fā)送前統(tǒng)一執(zhí)行某些操作,比如在請(qǐng)求頭中添加 token 字段。
          • 響應(yīng)攔截器:該類攔截器的作用是在接收到服務(wù)器響應(yīng)后統(tǒng)一執(zhí)行某些操作,比如發(fā)現(xiàn)響應(yīng)狀態(tài)碼為 401 時(shí),自動(dòng)跳轉(zhuǎn)到登錄頁(yè)。

          3.1 定義輔助函數(shù)

          在配置請(qǐng)求攔截器和響應(yīng)攔截器前,阿寶哥先來定義 3 個(gè)輔助函數(shù):

          • generateReqKey:用于根據(jù)當(dāng)前請(qǐng)求的信息,生成請(qǐng)求 Key;
          function generateReqKey(config{
            const { method, url, params, data } = config;
            return [method, url, Qs.stringify(params), Qs.stringify(data)].join("&");
          }
          • addPendingRequest:用于把當(dāng)前請(qǐng)求信息添加到pendingRequest對(duì)象中;
          const pendingRequest = new Map();
          function addPendingRequest(config{
            const requestKey = generateReqKey(config);
            config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {
              if (!pendingRequest.has(requestKey)) {
                 pendingRequest.set(requestKey, cancel);
              }
            });
          }
          • removePendingRequest:檢查是否存在重復(fù)請(qǐng)求,若存在則取消已發(fā)的請(qǐng)求。
          function removePendingRequest(config{
            const requestKey = generateReqKey(config);
            if (pendingRequest.has(requestKey)) {
               const cancelToken = pendingRequest.get(requestKey);
               cancelToken(requestKey);
               pendingRequest.delete(requestKey);
            }
          }

          創(chuàng)建好 generateReqKeyaddPendingRequestremovePendingRequest 函數(shù)之后,我們就可以設(shè)置請(qǐng)求攔截器和響應(yīng)攔截器了。

          3.2 設(shè)置請(qǐng)求攔截器

          axios.interceptors.request.use(
            function (config{
              removePendingRequest(config); // 檢查是否存在重復(fù)請(qǐng)求,若存在則取消已發(fā)的請(qǐng)求
              addPendingRequest(config); // 把當(dāng)前請(qǐng)求信息添加到pendingRequest對(duì)象中
              return config;
            },
            (error) => {
               return Promise.reject(error);
            }
          );

          3.3 設(shè)置響應(yīng)攔截器

          axios.interceptors.response.use(
            (response) => {
               removePendingRequest(response.config); // 從pendingRequest對(duì)象中移除請(qǐng)求
               return response;
             },
             (error) => {
                removePendingRequest(error.config || {}); // 從pendingRequest對(duì)象中移除請(qǐng)求
                if (axios.isCancel(error)) {
                  console.log("已取消的重復(fù)請(qǐng)求:" + error.message);
                } else {
                  // 添加異常處理
                }
                return Promise.reject(error);
             }
          );

          由于完整的示例代碼內(nèi)容比較多,阿寶哥就不放具體的代碼了。感興趣的小伙伴,可以訪問以下地址瀏覽示例代碼。

          完整的示例代碼:https://gist.github.com/semlinker/e426780664f0186db434882f1e27ac3a

          這里我們來看一下 Axios 取消重復(fù)請(qǐng)求示例的運(yùn)行結(jié)果:

          從上圖可知,當(dāng)出現(xiàn)重復(fù)請(qǐng)求時(shí),之前已發(fā)送且未完成的請(qǐng)求會(huì)被取消掉。下面我們用一張流程圖來總結(jié)一下取消重復(fù)請(qǐng)求的處理流程:

          最后,我們來回答前面留下的問題,即 CancelToken 內(nèi)部是如何工作的?

          四、CancelToken 的工作原理

          在前面的示例中,我們是通過調(diào)用 CancelToken 構(gòu)造函數(shù)來創(chuàng)建 CancelToken 對(duì)象:

          new axios.CancelToken((cancel) => {
            if (!pendingRequest.has(requestKey)) {
              pendingRequest.set(requestKey, cancel);
            }
          })

          所以接下來,我們來分析 CancelToken 構(gòu)造函數(shù),該函數(shù)被定義在 lib/cancel/CancelToken.js 文件中:

          // lib/cancel/CancelToken.js
          function CancelToken(executor{
            if (typeof executor !== 'function') {
              throw new TypeError('executor must be a function.');
            }

            var resolvePromise;
            this.promise = new Promise(function promiseExecutor(resolve{
              resolvePromise = resolve;
            });

            var token = this;
            executor(function cancel(message// 設(shè)置cancel對(duì)象
              if (token.reason) {
                return// Cancellation has already been requested
              }
              token.reason = new Cancel(message);
              resolvePromise(token.reason);
            });
          }

          由以上代碼可知,cancel 對(duì)象是一個(gè)函數(shù),當(dāng)我們調(diào)用該函數(shù)后,會(huì)創(chuàng)建 Cancel 對(duì)象并調(diào)用 resolvePromise 方法。該方法執(zhí)行后,CancelToken 對(duì)象上 promise 屬性所指向的 promise 對(duì)象的狀態(tài)將變?yōu)?resolved。那么這樣做的目的是什么呢?這里我們從 lib/adapters/xhr.js 文件中找到了答案:

          // lib/adapters/xhr.js 
          if (config.cancelToken) {
            config.cancelToken.promise.then(function onCanceled(cancel{
              if (!request) { return; }
              request.abort(); // 取消請(qǐng)求
              reject(cancel);
              request = null;
            });
          }

          看完上述的內(nèi)容,可能有的小伙伴還不是很能理解 CancelToken 的工作原理,所以阿寶哥又畫了一張圖來幫助大家理解 CancelToken 的工作原理:

          五、總結(jié)

          本文介紹了在 Axios 中如何取消重復(fù)請(qǐng)求及 CancelToken 的工作原理,在后續(xù)的文章中,阿寶哥將會(huì)介紹在 Axios 中如何設(shè)置數(shù)據(jù)緩存,感興趣的小伙伴不要錯(cuò)過喲。如果你想了解 Axios 中 HTTP 攔截器及 HTTP 適配器的設(shè)計(jì)與實(shí)現(xiàn),可以閱讀 77.9K 的 Axios 項(xiàng)目有哪些值得借鑒的地方 這篇文章。

          六、參考資源


          瀏覽 69
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  澳门AV在线 | 国产v夜色观看 | 免费黄网站在线观看 | 青娱乐极品黄色视频 | 五月丁香亭亭操逼 |