1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 22964字,需瀏覽 46分鐘

         ·

        2021-11-15 11:54

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

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

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

        幾乎在所有面向用戶(hù)或企業(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)給用戶(hù)之前,前后端開(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)等一系列流程之后才具有可用性,才能展現(xiàn)在用戶(hù)面前供用戶(hù)使用。

        但是可能并不是在任何場(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ì)不停占用客戶(hù)端和服務(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ò)慢,用戶(hù)可能期間發(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í)輪詢(xún)業(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ò)亂;
        • 類(lèi)似于關(guān)鍵字搜索或模糊查詢(xú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)行包裝,減少查詢(xú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)求,減少客戶(hù)端和服務(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)稱(chēng)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));

        基于以上操作便完成了一次客戶(hù)端和服務(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管道鏈中還為我們暴露了稱(chēng)之為攔截器的入口,使得我們可以參與到一個(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)景,上面的示例顯然不能滿(mǎn)足我們的需求。這里我們同樣可以利用上一小節(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)讲僮?,同時(shí)還支持直接傳入Request對(duì)象來(lái)發(fā)送請(qǐng)求,增加了很強(qiáng)的靈活性。

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

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

        // 安裝依賴(lài)
        npm install --save whatwg-fetch

        // 引入依賴(lài)
        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è)方法onabort,aborted屬性體現(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í)感興趣的話(huà)(后續(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)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            黄色视频在线免费观看视频 | 中文字幕激情无码 | 天天撸网站 | 久久偷看各类wc女厕嘘嘘污黄 | 可以免费看的黄色 | 攵女乱h系列合集多女图片 | 久久婷色| 奇米色色网| 亚洲国产性爱 | 日韩欧美在线观看不卡 |