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

          如何優(yōu)雅的管理 HTTP 請(qǐng)求和響應(yīng)攔截器?【經(jīng)驗(yàn)總結(jié)篇】

          共 10099字,需瀏覽 21分鐘

           ·

          2021-07-29 14:32

          本文思路來自實(shí)際項(xiàng)目的重構(gòu)總結(jié),歡迎糾正和交流。

          最近重構(gòu)一個(gè)老項(xiàng)目,發(fā)現(xiàn)其中處理請(qǐng)求的攔截器寫得相當(dāng)亂,于是我將整個(gè)項(xiàng)目的請(qǐng)求處理層重構(gòu)了,目前已經(jīng)在項(xiàng)目中正常運(yùn)行。

          本文會(huì)和大家分享我的重構(gòu)思路和后續(xù)優(yōu)化的思考,為方便與大家分享,我用 Vue3 實(shí)現(xiàn)一個(gè)簡單 demo,思路是一致的,有興趣的朋友可以在我 Github 查看[1],本文會(huì)以這個(gè) Vue 實(shí)現(xiàn)的 demo 為例介紹。

          本文我會(huì)主要和大家分享以下幾點(diǎn):

          1. 問題分析和方案設(shè)計(jì);
          2. 重構(gòu)后效果;
          3. 開發(fā)過程;
          4. 后期優(yōu)化點(diǎn);

          如果你還不清楚什么是 HTTP 請(qǐng)求和響應(yīng)攔截器,那么可以先看看《77.9K Star 的 Axios 項(xiàng)目有哪些值得借鑒的地方》[2] 。

          一、需求思考和方案設(shè)計(jì)

          1. 問題分析

          目前舊項(xiàng)目經(jīng)過多位同事參與開發(fā),攔截器存在以下問題:

          • 代碼比較混亂,可讀性差;
          • 每個(gè)攔截器職責(zé)混亂,存在相互依賴;
          • 邏輯上存在問題;
          • 團(tuán)隊(duì)內(nèi)部不同項(xiàng)目無法復(fù)用;

          2. 方案設(shè)計(jì)

          分析上面問題后,我初步的方案如下:參考插件化架構(gòu)設(shè)計(jì),獨(dú)立每個(gè)攔截器,將每個(gè)攔截器抽離成單獨(dú)文件維護(hù),做到職責(zé)單一,然后通過攔截器調(diào)度器進(jìn)行調(diào)度和注冊(cè)。

          其攔截器調(diào)度過程如下圖:

          二、重構(gòu)后效果

          代碼其實(shí)比較簡單,這里先看下最后實(shí)現(xiàn)效果:

          1. 目錄分層更加清晰

          重構(gòu)后請(qǐng)求處理層的目錄分層更加清晰,大致如下:

          目錄分層

          2. 攔截器開發(fā)更加方便

          在后續(xù)業(yè)務(wù)拓展新的攔截器,僅需 3 個(gè)步驟既可以完成攔截器的開發(fā)和使用,攔截器調(diào)度器會(huì)自動(dòng)調(diào)用所有攔截器

          攔截器開發(fā)更加方便

          3. 每個(gè)攔截器職責(zé)更加單一,可插拔

          將每個(gè)攔截器抽成一個(gè)文件去實(shí)現(xiàn),讓每個(gè)攔截器職責(zé)分離且單一,當(dāng)不需要使用某個(gè)攔截器時(shí),隨時(shí)可以替換,靈活插拔。

          三、開發(fā)過程

          這里以我單獨(dú)抽出來的這個(gè) demo 項(xiàng)目[3]為例來介紹。

          1. 初始化目錄結(jié)構(gòu)

          按照前面設(shè)計(jì)的方案,首先需要在項(xiàng)目中創(chuàng)建一下目錄結(jié)構(gòu):

          - request
           - index.js      // 攔截器調(diào)度器
            - interceptors  
              - request     // 用來存放每個(gè)請(qǐng)求攔截器
               - index.js  // 管理所有請(qǐng)求攔截器,并做排序
              - response    // 用來存放每個(gè)響應(yīng)攔截器
               - index.js  // 管理所有響應(yīng)攔截器,并做排序

          2. 定義攔截器調(diào)度器

          因?yàn)轫?xiàng)目采用 axios 請(qǐng)求庫[4],所以我們需要先知道 axios 攔截器的使用方法,這里簡單看下 axios 文檔上如何使用攔截器[5]的:

          // 添加請(qǐng)求攔截器
          axios.interceptors.request.use(function (config{
              // 業(yè)務(wù) 邏輯
              return config;
            }, function (error{
              // 業(yè)務(wù) 邏輯
              return Promise.reject(error);
            });

          // 添加響應(yīng)攔截器
          axios.interceptors.response.use(function (response{
              // 業(yè)務(wù) 邏輯
              return response;
            }, function (error{
              // 業(yè)務(wù)邏輯
              return Promise.reject(error);
            });

          從上面代碼,我們可以知道,使用攔截器的時(shí)候,只需調(diào)用 axios.interceptors 對(duì)象上對(duì)應(yīng)方法即可,因此我們可以將這塊邏輯抽取出來:

          // src/request/interceptors/index.js
          import { log } from '../log';
          import request from './request/index';
          import response from './response/index';

          export const runInterceptors = instance => {
              log('[runInterceptors]', instance);
             if(!instance) return;

              // 設(shè)置請(qǐng)求攔截器
              for (const key in request) {
                  instance.interceptors.request
                      .use(config => request[key](config "key"));
              }

              // 設(shè)置響應(yīng)攔截器
              for (const key in response) {
                  instance.interceptors.response
                      .use(result => response[key](result "key"));
              }

              return instance;
          }

          這就是我們的核心攔截器調(diào)度器,目前實(shí)現(xiàn)導(dǎo)入所有請(qǐng)求攔截器和響應(yīng)攔截器后,通過 for 循環(huán),注冊(cè)所有攔截器,最后將整個(gè) axios 實(shí)例返回出去。

          3. 定義簡單的請(qǐng)求攔截器和響應(yīng)攔截器

          這里我們做簡單演示,創(chuàng)建以下兩個(gè)攔截器:

          1. 請(qǐng)求攔截器:setLoading,作用是在發(fā)起請(qǐng)求前,顯示一個(gè)全局 Toast 框,提示“加載中...”文案。
          2. 響應(yīng)攔截器:setLoading,作用是在請(qǐng)求響應(yīng)后,關(guān)閉頁面中的 Toast 框。

          為了統(tǒng)一開發(fā)規(guī)范,我們約定插件開發(fā)規(guī)范如下:

          /*
            攔截器名稱:xxx
          */

          const interceptorName = options => {
            log("[interceptor.request]interceptorName:", options);
           // 攔截器業(yè)務(wù)
            return options;
          };

          export default interceptorName;

          首先創(chuàng)建文件 src/request/interceptors/request/ 目錄下創(chuàng)建 setLoading.js  文件,按照上面約定的插件開發(fā)規(guī)范,我們完成下面插件開發(fā):

          // src/request/interceptors/request/setLoading.js

          import { Toast } from 'vant';
          import { log } from "../../log";

          /*
            攔截器名稱:全局設(shè)置請(qǐng)求的 loading 動(dòng)畫
          */

          const setLoading = options => {
            log("[interceptor.request]setLoading:", options);

            Toast.loading({
              duration0,
              message'加載中...',
              forbidClicktrue,
            });
            return options;
          };

          export default setLoading;

          然后在導(dǎo)出該請(qǐng)求攔截器,并且導(dǎo)出的是個(gè)數(shù)組,方便攔截器調(diào)度器進(jìn)行統(tǒng)一注冊(cè):

          // src/request/interceptors/request/index.js

          import setLoading from './setLoading';

          export default [
              setLoading
          ];

          按照相同方式,我們開發(fā)響應(yīng)攔截器:

          // src/request/interceptors/response/setLoading.js

          import { Toast } from 'vant';
          import { log } from "../../log";

          /*
            攔截器名稱:關(guān)閉全局請(qǐng)求的 loading 動(dòng)畫
          */

          const setLoading = result => {
            log("[interceptor.response]setLoading:", result);

            // example: 請(qǐng)求返回成功時(shí),關(guān)閉所有 toast 框
            if(result && result.success){
              Toast.clear();
            }
            return result;
          };

          export default setLoading;

          導(dǎo)出響應(yīng)攔截器:

          // src/request/interceptors/response/index.js

          import setLoading from './setLoading';
          export default [
              setLoading
          ];

          4. 全局設(shè)置 axios 攔截器

          按照前面相同步驟,我又多寫了幾個(gè)攔截器:請(qǐng)求攔截器:

          • setSecurityInformation.js:為請(qǐng)求的 url 添加安全參數(shù);
          • setSignature.js:為請(qǐng)求的請(qǐng)求頭添加加簽信息;
          • setToken.js:為請(qǐng)求的請(qǐng)求頭添加 token 信息;

          響應(yīng)攔截器:

          • setError.js:處理響應(yīng)結(jié)果的出錯(cuò)情況,如關(guān)閉所有 toast 框;
          • setInvalid.js:處理響應(yīng)結(jié)果的登錄失效情況,如跳轉(zhuǎn)到登錄頁;
          • setResult.js:處理響應(yīng)結(jié)果的數(shù)據(jù)嵌套太深的問題,將 result.data.data.data 這類返回結(jié)果處理成 result.data 格式;

          至于是如何實(shí)現(xiàn)的,大家有興趣可以在我 Github 查看[6]

          然后我們可以將 axios 進(jìn)行二次封裝,導(dǎo)出 request 對(duì)象供業(yè)務(wù)使用:

          // src/request/index.js

          import axios from 'axios';
          import { runInterceptors } from './interceptors/index';
          export const requestConfig = { timeout10000 };

          let request = axios.create(requestConfig);
          request = runInterceptors(request);

          export default request;

          到這邊就完成。

          在業(yè)務(wù)中需要發(fā)起請(qǐng)求,可以這么使用:

          <template>
          <div><button @click="send">發(fā)起請(qǐng)求</button></div>
          </template>

          <script setup>
          import request from './../request/index.js';

          const send = async () => {
          const result = await request({
          url: 'https://httpbin.org/headers',
          method: 'get'
          })
          }
          </script>

          5. 測試一下

          開發(fā)到這邊就差不多,我們發(fā)送個(gè)請(qǐng)求,可以看到所有攔截器執(zhí)行過程如下:

          日志輸出

          看看請(qǐng)求頭信息:

          請(qǐng)求頭

          可以看到我們開發(fā)的請(qǐng)求攔截器已經(jīng)生效。

          四、Taro 中使用

          由于 Taro[7] 中已經(jīng)提供了 Taro.request[8] 方法作為請(qǐng)求方法,我們可以不需要使用 axios 發(fā)請(qǐng)求。

          基于上面代碼進(jìn)行改造,也很簡單,只需要更改 2 個(gè)地方:

          1. 修改封裝請(qǐng)求的方法

          主要是更換 axios 為 Taro.request 方法,并使用 addInterceptor  方法導(dǎo)入攔截器:

          // src/request/index.js

          import Taro from "@tarojs/taro";
          import { runInterceptors } from './interceptors/index';

          Taro.addInterceptor(runInterceptors);

          export const request = Taro.request;
          export const requestTask = Taro.RequestTask; // 看需求,是否需要
          export const addInterceptor = Taro.addInterceptor; // 看需求,是否需要

          2. 修改攔截器調(diào)度器

          由于 axios 和 Taro.request 添加攔截器的方法不同,所以也需要進(jìn)行更換:

          import request from './interceptors/request';
          import response from './interceptors/response';

          export const interceptor = {
              request,
              response
          };

          export const getInterceptor = (chain = {}) => {
            // 設(shè)置請(qǐng)求攔截器
            let requestParams = chain.requestParams;
            for (const key in request) {
              requestParams = request[key](requestParams "key");
            }

            // 設(shè)置響應(yīng)攔截器
            let responseObject = chain.proceed(requestParams);
            for (const key in response) {
              responseObject = responseObject.then(res => response[key](res "key"));
            }
            return responseObject;
          };

          具體 API 可以看 Taro.request[9] 文檔,這里不過多介紹。

          五、項(xiàng)目總結(jié)和思考

          這次重構(gòu)主要是按照已有業(yè)務(wù)進(jìn)行重構(gòu),因此即使是重構(gòu)后的請(qǐng)求層,仍然還有很多可以優(yōu)化的點(diǎn),目前我想到有這些,也算是我的一個(gè) TODO LIST 了:

          1. 將請(qǐng)求層獨(dú)立成庫

          由于公司現(xiàn)在獨(dú)立站點(diǎn)的項(xiàng)目較多,考慮到項(xiàng)目的統(tǒng)一開發(fā)規(guī)范,可以考慮將該請(qǐng)求層獨(dú)立為私有庫進(jìn)行維護(hù)。目前思路:

          • 參考插件化架構(gòu)設(shè)計(jì),通過 lerna[10] 做管理所有攔截器;
          • 升級(jí) TypeScript,方便管理和開發(fā);
          • 進(jìn)行工程化改造,加入構(gòu)建工具、單元測試、UMD等等;
          • 使用文檔和開發(fā)文檔完善。

          2.  支持可更換請(qǐng)求庫

          單獨(dú)抽這一點(diǎn)來講,是因?yàn)槟壳拔覀兦岸藞F(tuán)隊(duì)使用的請(qǐng)求庫較多,比較分散,所以考慮到通用性,需要增加支持可更換請(qǐng)求庫方法。目前思路:

          • 在已有請(qǐng)求層再抽象一層請(qǐng)求庫適配層,定義統(tǒng)一接口;
          • 內(nèi)置幾種常見請(qǐng)求庫的適配。

          3. 開發(fā)攔截器腳手架

          這個(gè)的目的其實(shí)很簡單,讓團(tuán)隊(duì)內(nèi)其他人直接使用腳手架工具,按照內(nèi)置腳手架模版,快速創(chuàng)建一個(gè)攔截器,進(jìn)行后續(xù)開發(fā),很大程度統(tǒng)一攔截器的開發(fā)規(guī)范。目前思路:

          • 內(nèi)置兩套攔截器模版:請(qǐng)求攔截器和響應(yīng)攔截器;
          • 腳手架開發(fā)比較簡單,參數(shù)(如語言)根據(jù)業(yè)務(wù)需要再確定。

          4. 增強(qiáng)攔截器調(diào)度

          目前實(shí)現(xiàn)的這個(gè)功能還比較簡單,還是得考慮增強(qiáng)攔截器調(diào)度。目前思路:

          • 處理攔截器失敗的情況;
          • 處理攔截器調(diào)度順序的問題;
          • 攔截器同步執(zhí)行、異步執(zhí)行、并發(fā)執(zhí)行、循環(huán)執(zhí)行等等情況;
          • 可插拔的攔截器調(diào)度;
          • 考慮參考 Tapable 插件機(jī)制;

          六、本文總結(jié)

          本文通過一次簡單的項(xiàng)目重構(gòu)總結(jié)出一個(gè)請(qǐng)求層攔截器調(diào)度方案,目的是為了實(shí)現(xiàn)所有攔截器職責(zé)單一、方便維護(hù),并統(tǒng)一維護(hù)自動(dòng)調(diào)度,大大降低實(shí)際業(yè)務(wù)的攔截器開發(fā)上手難度。

          后續(xù)我仍有很多需要優(yōu)化的地方,作為自己的一個(gè) TODO LIST,如果是做成完全通用,則定位可能更偏向于攔截器調(diào)度容器,只提供一些通用攔截器,其余還是由開發(fā)者定義,庫負(fù)責(zé)調(diào)度,但常用的請(qǐng)求庫一般都已經(jīng)做好,所以這樣做的價(jià)值有待權(quán)衡。

          當(dāng)然,目前還是優(yōu)先作為團(tuán)隊(duì)內(nèi)部私有庫進(jìn)行開發(fā)和使用,因?yàn)榛旧蠄F(tuán)隊(duì)內(nèi)容使用的業(yè)務(wù)都差不多,只是項(xiàng)目不同。

          參考資料

          [1]

          在我 Github 查看: https://github.com/pingan8787/Leo-JavaScript/blob/master/Cute-Summary/useful-request-demo/index.html

          [2]

          《77.9K Star 的 Axios 項(xiàng)目有哪些值得借鑒的地方》: https://juejin.cn/post/6885471967714115597

          [3]

          這個(gè) demo 項(xiàng)目: https://github.com/pingan8787/Leo-JavaScript/blob/master/Cute-Summary/useful-request-demo/index.html

          [4]

          axios 請(qǐng)求庫: https://github.com/axios/axios

          [5]

          axios 文檔上如何使用攔截器: https://github.com/axios/axios#interceptors

          [6]

          在我 Github 查看: https://github.com/pingan8787/Leo-JavaScript/blob/master/Cute-Summary/useful-request-demo/index.html

          [7]

          Taro: https://taro-docs.jd.com/

          [8]

          Taro.request: https://taro-docs.jd.com/taro/docs/2.x/apis/network/request/request

          [9]

          Taro.request: https://taro-docs.jd.com/taro/docs/2.x/apis/network/request/request

          [10]

          lerna: https://github.com/lerna/lerna/


          往期精文

          微信搜索 全棧修煉,回復(fù) 電子書 就送你 1000+ 本精華編程電子書;回復(fù) 1024 送你一套完整的 前端 視頻教程,絕對(duì)免費(fèi),無套路獲取。
          瀏覽 103
          點(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>
                  99re99| 日韩一区三区 | 国产青青操视频 | 国产午夜精品123 | 高清无码做爱视频 |