<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>

          前端領(lǐng)域如何實(shí)現(xiàn)請(qǐng)求中斷

          共 22964字,需瀏覽 46分鐘

           ·

          2021-11-15 11:54

          大廠技術(shù)  高級(jí)前端  Node進(jìn)階

          點(diǎn)擊上方 程序員成長(zhǎng)指北,關(guān)注公眾號(hào)

          回復(fù)1,加入高級(jí)Node交流群

          幾乎在所有面向用戶或企業(yè)的應(yīng)用程序中,所呈現(xiàn)出來(lái)的信息都不是一成不變的,即數(shù)據(jù)都是動(dòng)態(tài)的,由某個(gè)或者多個(gè)后臺(tái)服務(wù)所提供。那么就不可避免地會(huì)涉及到網(wǎng)絡(luò)請(qǐng)求,而對(duì)于不同企業(yè)肯定有不同的業(yè)務(wù)場(chǎng)景。在一個(gè)功能完善的應(yīng)用程序呈現(xiàn)給用戶之前,前后端開(kāi)發(fā)人員必須先根據(jù)產(chǎn)品經(jīng)理提供的業(yè)務(wù)需求文檔協(xié)商建立起格式良好的接口契約,然后再經(jīng)過(guò)開(kāi)發(fā)聯(lián)調(diào)測(cè)試驗(yàn)證部署上線等一系列流程之后才具有可用性,才能展現(xiàn)在用戶面前供用戶使用。

          但是可能并不是在任何場(chǎng)景下,我們都需要關(guān)心網(wǎng)絡(luò)請(qǐng)求的響應(yīng)結(jié)果,或者說(shuō)在某些場(chǎng)景下,我們只需要關(guān)心最新的有效的網(wǎng)絡(luò)請(qǐng)求,對(duì)于老舊的失效的網(wǎng)絡(luò)請(qǐng)求,我們甚至可以忽略它的存在。我們知道,從瀏覽器發(fā)起一次網(wǎng)絡(luò)請(qǐng)求,到建立TCP鏈接(對(duì)于HTTPS協(xié)議還需要建立額外的TLS連接)以及DNS域名解析,再到發(fā)送請(qǐng)求數(shù)據(jù)報(bào)文,最終服務(wù)器處理請(qǐng)求并響應(yīng)數(shù)據(jù),期間會(huì)不停占用客戶端和服務(wù)器資源。如果該網(wǎng)絡(luò)請(qǐng)求對(duì)于我們而言已經(jīng)無(wú)效,那么我們就可以通過(guò)手動(dòng)中斷請(qǐng)求,來(lái)提前釋放被占用的資源,減少不必要的資源開(kāi)銷(xiāo)。

          例如考慮以下場(chǎng)景:

          • VueReact單頁(yè)應(yīng)用中,組件A掛載完畢之后向后臺(tái)服務(wù)發(fā)起請(qǐng)求拉取數(shù)據(jù),但是由于加載過(guò)慢,用戶可能期間發(fā)生路由跳轉(zhuǎn)或回退,導(dǎo)致組件A卸載,但是組件內(nèi)部的網(wǎng)絡(luò)請(qǐng)求并沒(méi)有立即停止下來(lái),此時(shí)的響應(yīng)數(shù)據(jù)對(duì)于已卸載的組件A而言已經(jīng)無(wú)效。若剛好此時(shí)請(qǐng)求響應(yīng)錯(cuò)誤,就可能導(dǎo)致前端實(shí)現(xiàn)的兜底彈窗出現(xiàn)在跳轉(zhuǎn)后的頁(yè)面中,造成視覺(jué)干擾;
          • 頁(yè)面存在定時(shí)輪詢業(yè)務(wù),即固定間隔一段時(shí)間再次發(fā)起請(qǐng)求,這樣就可能存在多個(gè)請(qǐng)求間的競(jìng)爭(zhēng)關(guān)系,如果上一個(gè)請(qǐng)求的響應(yīng)速度比最近一次請(qǐng)求的響應(yīng)速度慢,則前者就會(huì)覆蓋后者,從而導(dǎo)致數(shù)據(jù)錯(cuò)亂;
          • 類似于關(guān)鍵字搜索或模糊查詢等需要頻繁發(fā)起網(wǎng)絡(luò)請(qǐng)求的相關(guān)業(yè)務(wù),可能在一定程度上為了優(yōu)化程序的執(zhí)行性能,減少冗余的網(wǎng)絡(luò)IO,我們會(huì)使用防抖(debounce)函數(shù)來(lái)對(duì)請(qǐng)求邏輯進(jìn)行包裝,減少查詢次數(shù)以降低服務(wù)器壓力,但是依舊避免不了由于加載耗時(shí)過(guò)長(zhǎng)導(dǎo)致新老請(qǐng)求數(shù)據(jù)錯(cuò)亂的問(wèn)題;
          • 針對(duì)前端大文件上傳等上傳服務(wù),需要實(shí)現(xiàn)上傳進(jìn)度的暫停恢復(fù),即斷點(diǎn)續(xù)傳

          還有很多其他沒(méi)有列出的應(yīng)用場(chǎng)景,針對(duì)每種應(yīng)用場(chǎng)景,雖然我們都能給出對(duì)應(yīng)的方案來(lái)解決實(shí)際問(wèn)題,但是筆者認(rèn)為最理想的方案還是盡量減少無(wú)用請(qǐng)求,減少客戶端和服務(wù)器之間的無(wú)效傳輸,鑒于此也就引入了本文中將要講到的中斷請(qǐng)求的方式。

          在前端領(lǐng)域,個(gè)人覺(jué)得有幾種比較常見(jiàn)的網(wǎng)絡(luò)請(qǐng)求方案:瀏覽器原生支持的XMLHttpRequest對(duì)象同時(shí)兼容瀏覽器端和NodeJS服務(wù)端的第三方HTTP庫(kù)Axios大部分瀏覽器最新實(shí)現(xiàn)的Fetch API。本文主要基于以上三種請(qǐng)求方案講解一下各自中斷請(qǐng)求的方式,文中若有錯(cuò)誤,還請(qǐng)指正。

          1、XMLHttpRequest

          瀏覽器原生實(shí)現(xiàn)的XMLHttpRequest(以下簡(jiǎn)稱XHR)構(gòu)造函數(shù)對(duì)于我們來(lái)說(shuō)已經(jīng)是再熟悉不過(guò)了,但是在實(shí)際應(yīng)用中,大部分場(chǎng)景下可能我們并不需要去主動(dòng)實(shí)例化XHR構(gòu)造函數(shù),畢竟實(shí)例化之后還需要通過(guò)調(diào)用opensend等一系列的官方API才能實(shí)現(xiàn)與服務(wù)器的數(shù)據(jù)交互,操作細(xì)節(jié)稍微繁瑣。

          相反我們一般會(huì)推薦使用社區(qū)實(shí)現(xiàn)的第三方庫(kù)來(lái)方便我們簡(jiǎn)化操作流程,提升開(kāi)發(fā)效率,例如下一節(jié)將要講述的Axios。但即便是Axios,在瀏覽器端其底層依舊是通過(guò)XHR構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)網(wǎng)絡(luò)IO的,因此這一小節(jié)有必要對(duì)XHR的相關(guān)知識(shí)點(diǎn)進(jìn)行回顧和講解。

          首先拋出一個(gè)基礎(chǔ)示例:

          /**
           * @description: 基于 XHR 封裝的網(wǎng)絡(luò)請(qǐng)求工具函數(shù)
           * @param {String} url 請(qǐng)求接口地址
           * @param {Document | XMLHttpRequestBodyInit | null} body 請(qǐng)求體
           * @param {Object} requestHeader 請(qǐng)求頭
           * @param {String} method 請(qǐng)求方法
           * @param {String} responseType 設(shè)置響應(yīng)內(nèi)容的解析格式
           * @param {Boolean} async 請(qǐng)求是否異步
           * @param {Number} timeout 設(shè)置請(qǐng)求超時(shí)時(shí)間(單位:毫秒)
           * @param {Boolean} withCredentials 設(shè)置跨域請(qǐng)求是否允許攜帶 cookies 或 Authorization header 等授權(quán)信息
           * @return {Promise} 可包含響應(yīng)內(nèi)容的 Promise 實(shí)例
          */

          function request({
            url,
            body = null,
            requestHeader = {'Content-Type''application/x-www-form-urlencoded'},
            method = 'GET',
            responseType = 'text',
            async = true,
            timeout = 30000,
            withCredentials = false,
          } = {}
          {
            return new Promise((resolve, reject) => {
              if (!url) {
                return reject(new TypeError('the required parameter [url] is missing.'));
              }
              
              if (method.toLowerCase() === 'get' && body) {
                url += `?${request.serialize(body)}`;
                body = null;
              }

              const xhr = new XMLHttpRequest();
              xhr.open(method, url, async);

              if (async) {
                xhr.responseType = responseType;
                xhr.timeout = timeout;
              }
              xhr.withCredentials = withCredentials;

              if (requestHeader && typeof requestHeader === 'object') {
                Object.keys(requestHeader).forEach(key => xhr.setRequestHeader(key, requestHeader[key]));
              }

              xhr.onreadystatechange = function onReadyStateChange({
                if (xhr.readyState === XMLHttpRequest.DONE) {
                  if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
                    resolve(xhr.response);
                  }
                }
              };

              xhr.onerror = function onError(error{
                console.log(error);
                reject({ message'請(qǐng)求出錯(cuò),請(qǐng)稍后重試' });
              };

              xhr.ontimeout = function onTimeout({
                reject({ message'接口超時(shí),請(qǐng)稍后重試' });
              };

              xhr.send(body ? JSON.stringify(body) : null);
            });
          }

          以上示例對(duì)XHR請(qǐng)求操作流程進(jìn)行了一下簡(jiǎn)單的封裝,并未涉及到太多的細(xì)節(jié)和兼容處理。一個(gè)簡(jiǎn)單的調(diào)用方式如下:

          request({
            url'http://www.some-domain.com/path/to/example',
            method'POST',
            requestHeader: {'Content-Type''application/json; charset=UTF-8'},
            body: {key: value}
          }).then(response => console.log(response));

          基于以上操作便完成了一次客戶端和服務(wù)器的數(shù)據(jù)交互請(qǐng)求,接下來(lái)在此基礎(chǔ)上繼續(xù)完善請(qǐng)求中斷的相關(guān)邏輯。

          我們知道,在XHR實(shí)例上為我們提供了一個(gè)abort方法用于終止該請(qǐng)求,并且當(dāng)一個(gè)請(qǐng)求被終止的時(shí)候,該請(qǐng)求所對(duì)應(yīng)的XHR實(shí)例的readyState屬性將會(huì)被設(shè)置為XMLHttpRequest.UNSET(0),同時(shí)status屬性會(huì)被重置為0,因此在本示例中我們同樣使用abort方法來(lái)實(shí)現(xiàn)請(qǐng)求中斷。

          // 參考以上示例
          function request({
            // 省略入?yún)?br>  ...
          } = {}
          {
            return new Promise((resolve, reject) => {
              // 省略代碼
              ...
            });
          }

          // 存儲(chǔ)請(qǐng)求接口地址以及請(qǐng)求體和 XHR 實(shí)例的映射關(guān)系
          request.cache = {};

          /**
           * @description: 根據(jù)提供的鍵名中斷對(duì)應(yīng)的請(qǐng)求 
           * @param {String} key 存儲(chǔ)在 request.cache 屬性中的鍵名,若未提供則中斷全部請(qǐng)求 
           * @return {void}
           */

          request.clearCache = (key) => {
            if (key) {
              const instance = request.cache[key];
              if (instance) {
                instance.abort();
                delete request.cache[key];
              }

              return;
            }

            Object.keys(request.cache).forEach(cacheKey => {
              const instance = request.cache[cacheKey];
              instance.abort();
              delete request.cache[cacheKey];
            });
          };

          在以上示例中,我們通過(guò)request.cache來(lái)臨時(shí)存儲(chǔ)請(qǐng)求接口地址以及請(qǐng)求體和XHR實(shí)例的映射關(guān)系,因?yàn)樵谕豁?yè)面中一般可能會(huì)涉及到多個(gè)接口地址不同的請(qǐng)求,或者同一個(gè)請(qǐng)求對(duì)應(yīng)不同的請(qǐng)求體,因此這里考慮加上了請(qǐng)求體以做區(qū)分。當(dāng)然為了作為request.cache中的唯一鍵名,我們還需要對(duì)請(qǐng)求體進(jìn)行序列化操作,因此簡(jiǎn)單封裝一個(gè)序列化工具函數(shù)。

          /**
           * @description: 將請(qǐng)求體序列化為字符串
           * @param {Document | XMLHttpRequestBodyInit | null} data 請(qǐng)求體
           * @return {String} 序列化后的字符串
           */

          request.serialize = (data) => {
            if (data && typeof data === 'object') {
              const result = [];

              Object.keys(data).forEach(key => {
                result.push(`${key}=${JSON.stringify(data[key])}`);
              });

              return result.join('&');
            }

            return data;
          }

          完成以上的基礎(chǔ)代碼之后,接下來(lái)我們將其應(yīng)用到request函數(shù)中:

          function request({
            url,
            body = null,
            // 省略部分入?yún)?br>  ...
          } = {}
          {
            return new Promise((resolve, reject) => {
              if (!url) {
                return reject(new TypeError('the required parameter [url] is missing.'));
              }
              
              // 省略部分代碼
              ...

              const xhr = new XMLHttpRequest();

              // 將請(qǐng)求接口地址以及請(qǐng)求體和 XHR 實(shí)例存入 cache 中
              let cacheKey = url;
              if (body) {
                cacheKey += `_${request.serialize(body)}`;
              }

              // 每次發(fā)送請(qǐng)求之前將上一個(gè)未完成的相同請(qǐng)求進(jìn)行中斷
              request.cache[cacheKey] && request.clearCache(cacheKey);
              request.cache[cacheKey] = xhr;
              
              // 省略部分代碼
              ...

              xhr.onreadystatechange = function onReadyStateChange({
                if (xhr.readyState === XMLHttpRequest.DONE) {
                  if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
                    // 請(qǐng)求完成之后清除緩存
                    request.clearCache(cacheKey);
                    resolve(xhr.response);
                  }
                }
              };

              xhr.onerror = function onError(error{
                console.log(error);
                // 請(qǐng)求報(bào)錯(cuò)之后清除緩存
                request.clearCache(cacheKey);
                reject({ message'請(qǐng)求出錯(cuò),請(qǐng)稍后重試' });
              };

              xhr.ontimeout = function onTimeout({
                // 請(qǐng)求超時(shí)之后清除緩存
                request.clearCache(cacheKey);
                reject({ message'接口超時(shí),請(qǐng)稍后重試' });
              };

              xhr.send(body ? JSON.stringify(body) : null);
            });
          }

          這樣便簡(jiǎn)單實(shí)現(xiàn)了一個(gè)自包含的請(qǐng)求中斷的處理邏輯,每次發(fā)送請(qǐng)求之前自動(dòng)判定未完成的多余請(qǐng)求并將其清除,從而避免性能上的開(kāi)銷(xiāo)。當(dāng)然,不僅如此,這里同樣可以通過(guò)request.clearCache函數(shù)來(lái)在組件卸載或路由跳轉(zhuǎn)的時(shí)候手動(dòng)清除未完成的請(qǐng)求,因?yàn)檫@部分請(qǐng)求對(duì)于卸載后的組件而言沒(méi)有太多實(shí)質(zhì)意義,例如以下示例:

          // 網(wǎng)頁(yè)卸載前清除緩存
          window.addEventListener('beforeunload', () => request.clearCache(), false);

          // Vue 中路由跳轉(zhuǎn)前清除緩存
          router.beforeEach((to, from, next) => { request.clearCache(); next(); });

          // React 中路由跳轉(zhuǎn)時(shí)清除緩存
          import { Component } from 'react';
          import { withRouter } from 'react-router-dom';
          class App extends Component {
            componentDidMount() {
              // 監(jiān)聽(tīng)路由變化
              this.props.history.listen(location => {
                // 通過(guò)比較 location.pathname 來(lái)判定路由是否發(fā)生變化
                if (this.props.location.pathname !== location.pathname) {
                  // 若路由發(fā)生變化,則清除緩存
                  request.clearCache();
                }
              });
            }
          }

          export default withRouter(App);

          2、Axios

          Axios想必是我們使用最多的一個(gè)第三方開(kāi)源免費(fèi)的HTTP庫(kù),其本身基于Promise的特性使得我們可以很方便地寫(xiě)出更加優(yōu)雅且易維護(hù)的代碼,從而避免函數(shù)多層嵌套所帶來(lái)的一系列問(wèn)題。

          當(dāng)然,它最大的特點(diǎn)在于可以同時(shí)兼容瀏覽器端和NodeJS服務(wù)端。底層通過(guò)判定不同的運(yùn)行環(huán)境來(lái)自動(dòng)提供不同的適配器,在瀏覽器端通過(guò)原生的XHR對(duì)象來(lái)發(fā)送請(qǐng)求,而在NodeJS服務(wù)端則通過(guò)內(nèi)置的http模塊來(lái)發(fā)送請(qǐng)求。不僅如此,在其底層的Promise管道鏈中還為我們暴露了稱之為攔截器的入口,使得我們可以參與到一個(gè)請(qǐng)求的生命周期中,在請(qǐng)求發(fā)送之前和響應(yīng)接收之后能夠自定義實(shí)現(xiàn)數(shù)據(jù)的裝配和轉(zhuǎn)換操作。帶來(lái)的如此之多的人性化操作,使得我們沒(méi)有理由不去用它,這也奠定了其長(zhǎng)久以來(lái)依舊如此火爆的基礎(chǔ)。

          言歸正傳,在Axios中同樣為我們提供了請(qǐng)求中斷的相關(guān)API。首先拋出一個(gè)基礎(chǔ)示例:

          // 安裝 axios
          npm install --save axios

          // 導(dǎo)入 axios
          import axios from 'axios';
          // 創(chuàng)建 axios 實(shí)例
          const instance = axios.create({
            baseURL'https://www.some-domain.com/path/to/example',
            timeout30000,
            headers: {
              'Content-Type''application/x-www-form-urlencoded',
            },
          });
          // 設(shè)置 axios 實(shí)例默認(rèn)配置
          instance.defaults.headers.common['Authorization'] = '';
          instance.defaults.headers.post['Content-Type'] = 'application/json; charset=UTF-8';

          // 自定義請(qǐng)求攔截器
          instance.interceptors.request.use(config => {
            const token = window.localStorage.getItem('token');
            token && (config.headers['Authorization'] = token);
            return config;
          }, error => Promise.reject(error));

          // 自定義響應(yīng)攔截器
          instance.interceptors.response.use(response => {
            if (response.status === 200) {
              return Promise.resolve(response.data);
            }
            
            return Promise.reject(response);
          }, error => Promise.reject(error));

          接下來(lái)我們結(jié)合Axios提供的CancelToken構(gòu)造函數(shù)來(lái)創(chuàng)建一個(gè)簡(jiǎn)單的post請(qǐng)求:

          const CancelToken = axios.CancelToken;
          let cancel;

          instance.post('/api/user/123', {
            name'new name',
            phone'new phone',
          }, {
            // CancelToken 構(gòu)造函數(shù)接收一個(gè) executor 函數(shù)參數(shù),并且該函數(shù)接收一個(gè)取消函數(shù) c 用于取消該次請(qǐng)求
            cancelTokennew CancelToken(function executor(c{
              // 將取消函數(shù)賦值到外部變量,方便從外部取消請(qǐng)求
              cancel = c;
            }),
          });

          // 手動(dòng)取消請(qǐng)求
          cancel();

          針對(duì)需要同時(shí)取消多個(gè)請(qǐng)求以及自動(dòng)取消的應(yīng)用場(chǎng)景,上面的示例顯然不能滿足我們的需求。這里我們同樣可以利用上一小節(jié)的思路來(lái)維護(hù)一個(gè)請(qǐng)求接口地址以及請(qǐng)求體和取消函數(shù)c之間的映射關(guān)系。同時(shí)為了避免在每個(gè)請(qǐng)求中都需要手動(dòng)去實(shí)例化CancelToken,我們可以巧妙利用request攔截器來(lái)整合這部分的邏輯,實(shí)現(xiàn)邏輯復(fù)用。首先我們將緩存邏輯拆分到一個(gè)單獨(dú)的文件中:

          // cacheUtils.js
          export const CacheUtils = {
            // 存儲(chǔ)請(qǐng)求接口地址以及請(qǐng)求體和取消函數(shù)之間的映射關(guān)系
            cache: {},
            
            // 根據(jù)提供的鍵名 key 取消對(duì)應(yīng)的請(qǐng)求,若未提供則取消全部請(qǐng)求
            clearCachefunction (key{
              if (key) {
                const cancel = this.cache[key];
                if (cancel && typeof cancel === 'function') {
                  cancel();
                  delete this.cache[key];
                }

                return;
              }

              Object.keys(this.cache).forEach(cacheKey => {
                const cancel = this.cache[cacheKey];
                cancel();
                delete this.cache[cacheKey];
              });
            },
          };

          接下來(lái)我們將其應(yīng)用到請(qǐng)求攔截器和響應(yīng)攔截器中:

          import qs from 'qs';
          import { CacheUtils } from './cacheUtils.js';

          // 自定義請(qǐng)求攔截器
          instance.interceptors.request.use(config => {
            let cacheKey = config.url;
            
            const token = window.localStorage.getItem('token');
            token && (config.headers['Authorization'] = token);
            
            const method = config.method.toLowerCase();
            if (method === 'get' && config.params && typeof config.params === 'object') {
              cacheKey += qs.stringify(config.params, { addQueryPrefixtrue });
            }
            
            if (['post''put''patch'].includes(method) && config.data && typeof config.data === 'object') {
              config.data = qs.stringify(config.data);
              cacheKey += `_${qs.stringify(config.data, { arrayFormat: 'brackets' })}`;
            }
            
            // 每次發(fā)送請(qǐng)求之前將上一個(gè)未完成的相同請(qǐng)求進(jìn)行中斷
            CacheUtils.cache[cacheKey] && CacheUtils.clearCache(cacheKey);
            
            // 將當(dāng)前請(qǐng)求所對(duì)應(yīng)的取消函數(shù)存入緩存
            config.cancelToken = new axios.CancelToken(function executor(c{
              CacheUtils.cache[cacheKey] = c;
            });
            
            // 臨時(shí)保存 cacheKey,用于在響應(yīng)攔截器中清除緩存
            config.cacheKey = cacheKey;
            
            return config;
          }, error => Promise.reject(error));

          // 自定義響應(yīng)攔截器
          instance.interceptors.response.use(response => {
            // 響應(yīng)接收之后清除緩存
            const cacheKey = response.config.cacheKey;
            delete CacheUtils.cache[cacheKey];
            
            if (response.status === 200) {
              return Promise.resolve(response.data);
            }
            
            return Promise.reject(response);
          }, error => {
            // 響應(yīng)異常清除緩存
            if (error.config) {
              const cacheKey = error.config.cacheKey;
              delete CacheUtils.cache[cacheKey];
            }
            
            return Promise.reject(error);
          });

          這里我們同樣提供CacheUtils.clearCache函數(shù)來(lái)應(yīng)對(duì)需要手動(dòng)清除未完成請(qǐng)求的應(yīng)用場(chǎng)景,使用方式與上一小節(jié)思路相同,這里就不再重復(fù)多講。

          3、Fetch API

          作為瀏覽器原生提供的XHR構(gòu)造函數(shù)的理想替代方案,新增的Fetch API為我們提供了RequestResponse(以及其他與網(wǎng)絡(luò)請(qǐng)求有關(guān)的)對(duì)象的通用定義,一個(gè)Request對(duì)象表示一個(gè)資源請(qǐng)求,通常包含一些初始數(shù)據(jù)和正文內(nèi)容,例如資源請(qǐng)求路徑、請(qǐng)求方式、請(qǐng)求主體等,而一個(gè)Response對(duì)象則表示對(duì)一次請(qǐng)求的響應(yīng)數(shù)據(jù)。

          同時(shí)Fetch API還為我們提供了一個(gè)全局的fetch方法,通過(guò)該方法我們可以更加簡(jiǎn)單合理地跨網(wǎng)絡(luò)異步獲取資源。fetch方法不僅原生支持Promise的鏈?zhǔn)讲僮鳎瑫r(shí)還支持直接傳入Request對(duì)象來(lái)發(fā)送請(qǐng)求,增加了很強(qiáng)的靈活性。

          到目前為止,Fetch API的支持程度如下圖:

          不難看出IE瀏覽器下的兼容性不容樂(lè)觀,但是作為一名有追求的前端開(kāi)發(fā)人員,當(dāng)然不會(huì)止步于此。一番探索之后,發(fā)現(xiàn)可以通過(guò)isomorphic-fetch或者whatwg-fetch這兩個(gè)第三方依賴來(lái)解決兼容性問(wèn)題:

          // 安裝依賴
          npm install --save whatwg-fetch

          // 引入依賴
          import {fetch as fetchPolyfill} from 'whatwg-fetch';

          接下來(lái)同樣先拋出一個(gè)基礎(chǔ)示例:

          const url = 'http://www.some-domain.com/path/to/example';
          const initData = {
            method'POST',
            bodyJSON.stringify({key: value}),
            headers: {
              'Content-Type''application/json; charset=UTF-8',
            },
            cache'no-cache',
            credentials'same-origin',
            mode'cors',
            redirect'follow',
            referrer'no-referrer',
          };
          fetch(url, initData).then(response => response.json()).then(data => console.log(data));
            
          // 也可以直接通過(guò) Request 構(gòu)造函數(shù)來(lái)初始化請(qǐng)求數(shù)據(jù)
          // Request 構(gòu)造函數(shù)接收兩個(gè)參數(shù)
          // 第一個(gè)參數(shù)表示需要獲取的資源 URL 路徑或者另一個(gè)嵌套的 Request 實(shí)例
          // 第二個(gè)可選參數(shù)表示需要被包含到請(qǐng)求中的各種自定義選項(xiàng)
          const request = new Request(url, initData);
          fetch(request).then(response => response.json()).then(data => console.log(data));

          可以看到,相比于傳統(tǒng)的XHR方式而言,fetch函數(shù)的使用方式更加簡(jiǎn)潔友好,易用性更強(qiáng),同時(shí)還為我們提供了多種入?yún)⒌男问绞沟贸绦蚬δ茏兊酶拥撵`活可擴(kuò)展。

          那么回到本文的主題,上文中提到,在XHR實(shí)例中可以通過(guò)abort方法來(lái)取消請(qǐng)求,在Axios中可以通過(guò)CancelToken構(gòu)造函數(shù)的參數(shù)來(lái)獲得取消函數(shù),從而通過(guò)取消函數(shù)來(lái)取消請(qǐng)求。但是很遺憾的是,在Fetch API中,并沒(méi)有自帶的取消請(qǐng)求的API供我們調(diào)用。不過(guò)令人愉悅的是,除了IE瀏覽器外,其他瀏覽器已經(jīng)為Abort API添加了實(shí)驗(yàn)性支持,Abort API允許對(duì)XHRfetch這樣的請(qǐng)求操作在未完成時(shí)進(jìn)行終止,那么接下來(lái)對(duì)Abort API做一下簡(jiǎn)要的介紹。

          Abort API的相關(guān)概念中主要包含了AbortControllerAbortSignal兩大接口:

          • AbortController:表示一個(gè)控制器對(duì)象,該對(duì)象擁有一個(gè)只讀屬性signal和一個(gè)方法abortsignal屬性表示一個(gè)AbortSignal實(shí)例,當(dāng)我們需要取消某一個(gè)請(qǐng)求時(shí),需要將該signal屬性所對(duì)應(yīng)的AbortSignal實(shí)例與請(qǐng)求進(jìn)行關(guān)聯(lián),然后通過(guò)控制器對(duì)象提供的abort方法來(lái)取消請(qǐng)求;
          • AbortSignal:表示一個(gè)信號(hào)對(duì)象,作為控制器對(duì)象和請(qǐng)求之間通信的橋梁,允許我們通過(guò)控制器對(duì)象來(lái)對(duì)請(qǐng)求進(jìn)行取消操作。該對(duì)象擁有一個(gè)只讀屬性aborted和一個(gè)方法onabortaborted屬性體現(xiàn)為一個(gè)布爾值,表示與之通信的請(qǐng)求是否已經(jīng)被終止,而onabort方法會(huì)在控制器對(duì)象終止該請(qǐng)求時(shí)調(diào)用。

          通過(guò)以上兩個(gè)接口,我們嘗試封裝一個(gè)簡(jiǎn)單加強(qiáng)版的可取消的fetch工具函數(shù):

          const abortableFetch = (url, initData) => {
            // 實(shí)例化控制器對(duì)象
            const abortController = new AbortController();
            
            // 獲取信號(hào)對(duì)象
            const signal = abortController.signal;
            
            return {
              // 注意這里需要將 signal 信號(hào)對(duì)象與請(qǐng)求進(jìn)行關(guān)聯(lián),關(guān)聯(lián)之后才能通過(guò) abortController.abort 方法取消請(qǐng)求
              ready: fetch(url, {...initData, signal}).then(response => response.json()),
              // 暴露 cancel 方法,用于在外層手動(dòng)取消請(qǐng)求
              cancel() => abortController.abort(),
            };
          };

          并將其應(yīng)用到之前的基礎(chǔ)示例中:

          const url = 'http://www.some-domain.com/path/to/example';
          const initData = {
            method'POST',
            bodyJSON.stringify({key: value}),
            headers: {
              'Content-Type''application/json; charset=UTF-8',
            },
            cache'no-cache',
            credentials'same-origin',
            mode'cors',
            redirect'follow',
            referrer'no-referrer',
          };

          const {ready, cancel} = abortableFetch(url, initData);
          ready
            .then(response => console.log(response))
            .catch(err => {
              if (err.name === 'AbortError') {
                console.log('請(qǐng)求已被終止');
              }
            });

          // 手動(dòng)取消請(qǐng)求
          cancel();

          至此我們便成功完成了基于Abort API的請(qǐng)求中斷邏輯,當(dāng)然如果針對(duì)需要同時(shí)取消多個(gè)請(qǐng)求以及自動(dòng)取消的應(yīng)用場(chǎng)景,在abortableFetch函數(shù)中我們已經(jīng)對(duì)外暴露了cancel方法,是不是想起來(lái)在第二小節(jié)介紹Axios的過(guò)程中,同樣出現(xiàn)過(guò)cancel方法, 所以這里完全可以借助上文中的思路,構(gòu)建出請(qǐng)求路徑與請(qǐng)求體以及cancel取消函數(shù)之間的映射關(guān)系,對(duì)緩存進(jìn)行集中管理并對(duì)外提供清空緩存的工具方法,由于實(shí)現(xiàn)思路與上文中的大同小異,這里就不再展開(kāi)細(xì)講,感興趣的小伙伴兒可以自己嘗試下。

          總結(jié)

          這里我們?cè)俅位仡櫼幌卤疚闹饕v解的內(nèi)容,本文主要是基于目前前端領(lǐng)域使用的幾種比較常見(jiàn)的網(wǎng)絡(luò)請(qǐng)求方案,講解了一下在代碼層面各自實(shí)現(xiàn)請(qǐng)求中斷的處理方式。在瀏覽器原生提供的XHR對(duì)象中,我們通過(guò)實(shí)例上的abort方法來(lái)終止請(qǐng)求。在Axios庫(kù)中,我們借助于其提供的CancelToken構(gòu)造函數(shù)同樣實(shí)現(xiàn)了請(qǐng)求中斷。最后,我們通過(guò)fetch函數(shù)和Abort API的相互配合,實(shí)現(xiàn)了在現(xiàn)代主流瀏覽器的Fetch API中請(qǐng)求中斷的方式。通過(guò)這些優(yōu)化操作可以提前釋放被占用的資源,一定程度上減少了不必要的資源開(kāi)銷(xiāo)。

          Node 社群


          我組建了一個(gè)氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對(duì)Node.js學(xué)習(xí)感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。


             “分享、點(diǎn)贊在看” 支持一波??

          瀏覽 50
          點(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>
                  亚洲最新视频 | 91无码少妇a v久久欧美 | 日本黄色视频免费网站 | 久久久av影院 | 免费啪啪网站。 |