umi-request 中間件和攔截器解析


前言

使用react框架中經常使用到umi來搭建管理我們的項目,其中涉及到請求的模塊,umi自身提供了 umi-request 庫 方便了我們做網絡請求, umi-request的官方文檔可見其githubREADME.MD文件, 大部分功能都在readme中查詢,大部分功能已經將的很清楚了,單獨拿出 middlewareinterceptors 這兩個概念講一下。

為什么是 middlewareinterceptors

在使用umi-request的過程中最常見的需求是,對網絡請求的攔截,在請求前或請求后做一些事情,例如URL添加前綴,過濾無效參數,上報接口錯誤,頁面錯誤統一處理,單獨接口定制化處理等..., middleware (中間件) 和 interceptors (攔截器) 以及 errorHandler 是實現此類需求繞不過去的概念,雖然他們都能影響請求和返回結果,但是哪種場景下使用哪個函數?需要了解其實如何實現的,以及代碼的前后執行邏輯。

核心代碼

  // onion/index.js
  execute(params = null) {
    const fn = compose([
      ...this.middlewares,
      ...this.defaultMiddlewares,
      ...Onion.globalMiddlewares,
      ...Onion.coreMiddlewares,
    ]);
    return fn(params);
  }
 
  1. middlewares 和 interceptors.response 的關系 偽代碼就是 middleware(ctx) ctx 對象中包含responseInterceptors函數 ,也就是說responseInterceptors函數 會作為 middleware的參數使用
  2. compose函數會把middlewares中的函數按照順序串聯成一個可以遞歸調用的函數,fn(params) 會把這個遞歸調用函數執行掉

關於middleware

  • 使用

    • request.use(fn) // 默認加載到 onion實例上
    • request.use(fn, { defaultInstance: true })
    • request.use(fn, { global: true })
    • request.use(fn, { core: true })
  • 調用邏輯:extend(configObj)生成request實例,request初始化中生成一個Core實例,Core實例初始化中使用一個Onion實例, 我們調用的request.use實際上是Onion實例上的use方法

  • use方法的第二個參數是可選參數,根據這個配置決定這個middleware加載到哪一層,調用的順序也是不一樣的,具體查看官方給的示例,

  • request.use(fn) 默認加載到 onion實例上,執行的時候會首先進入這一類的middleware

  • request.use(fn, { defaultInstance: true }) 官方給出的描述是 “默認實例中間件,供開發者使用”,此類middleware還是會加載到Onion實例上,默認Core實例中給出了"[]"的配置

  • request.use(fn, { global: true }) ,全局中間件中本身就內置了三個函數 [simplePost, simpleGet, parseResponseMiddleware]

    • simplePost實現了默認請求頭設置, 'Content-Type': 'application/json;charset=UTF-8' 自動配置form類型的請求頭; 功能在next()前;
    • simpleGet實現了get請求使用params參數自動序列化;功能在next()前;
    • parseResponseMiddleware實現了Http Status Code 的判斷 copy.status >= 200 && copy.status < 300 之外的code會作為錯誤被拋出; 功能在next()之后;
    • 自己加入的middleware會放在這三個后,執行的順序用洋蔥模型理解一下就是“后進先出”
  • request.use(fn, { core: true }), 核心中間件內置了一個函數 [fetchMiddleware], 也是umi-request真正發起接口調用的地方,使用的是瀏覽器原生的fetch對象

  • fetchMiddleware 實現了瀏覽器兼容性判斷,緩存數據獲取,超時邏輯,取消請求功能;在原生fetch請求回的第一時間,就進入了responseInterceptors的函數,所以interceptors.response.use進來的攔截器函數是第一批接觸到真正fetch返回的response對象的函數,此時的response 對象是沒有經過包裝的原生fetch請求回來的對象,對象上常用的屬性有status, statusText, ok等屬性

關於Interceptors 攔截器

  • Interceptor分為兩部分,請求前的攔截,請求后的攔截
  • 請求前的攔截,是在進入洋蔥模型之前執行的函數,也就是說interceptors.request.use中的函數,會比任何middleware中的函數執行的早
   return new Promise((resolve, reject) => {
      this.dealRequestInterceptors(obj) // 在此處完成了interceptors.request.use中函數的執行
        .then(() => onion.execute(obj))
        .then(() => {
          resolve(obj.res);
        })
        .catch(error => {
        ...
    });

  • 請求前的攔截可以操作的屬性或對象只有 url 和 options 兩個,只對這樣個對象進行操作
  • 請求后的攔截是在 onion洋蔥模型的 最深處 執行,也就是上面提到的fetchMiddleware在獲取到fetch response 對象的第一時間會交給interceptors.response.use中的攔截函數處理

errorHandler

  • errorHandler執行的時機最晚,在各種攔截器各種中間件執行完成以后,這個時候如果捕獲到了異常才執行。
  • 默認的errorHandler的邏輯是,如果有配置errorHandler,errorHandler墊底,如果沒有配置errorhandle會直接拋出異常;
  • 單個請求option的 errorHandler 會覆蓋 extend(config) 中的 config.errorHandler, 這屬於option覆蓋的邏輯,不展開細講,有不懂的可以看一下官方文檔或源碼

middleware 和 interceptor 可以拿到的參數

  • 先看下ctx 對象
 const obj = {
      req: { url, options },
      res: null,
      cache: this.mapCache,
      responseInterceptors: [...Core.responseInterceptors, ...this.instanceResponseInterceptors],
    };
  • middleware(ctx, next) 函數 next 表示進入到下一層(里面一層),next 前的邏輯和next后的邏輯,按照洋蔥的進出的定義來理解,ctx 就是上述ctx對象
  • requestIntercetor的參數是 url 和options配置
  • responseIntercetor的參數是 原生的fetch產生的res對象

最后結合官方的示例理解一下執行順序

request.use(async (ctx, next) => {
  console.log('instanceA1');
  await next();
  console.log('instanceA2');
});
request.use(async (ctx, next) => {
  console.log('instanceB1');
  await next();
  console.log('instanceB2');
});
request.use(
  async (ctx, next) => {
    console.log('globalA1');
    await next();
    console.log('globalA2');
  },
  { global: true }
);
request.use(
  async (ctx, next) => {
    console.log('coreA1');
    await next();
    console.log('coreA2');
  },
  { core: true }
);

// 執行順序是
instanceA1 -> instanceB1 -> globalA1 -> coreA1 -> coreA2 -> globalA2 -> instanceB2 -> instanceA2

再加上我們上面的理解可以理解成這樣

// 看一下fetch的位置
instanceA1 -> instanceB1 -> globalA1 -> coreA1 -> fetch() -> coreA2 -> globalA2 -> instanceB2 -> instanceA2

// 看一下內置中間件 + 攔截器的執行順序 
requestInterceptor -> 自定義middleware -> simplePost-> simpleGet-> parseResponseMiddleware -> fetch -> responseInterceptor 
自定義middleware <- simplePost <- simpleGet <- parseResponseMiddleware  <-

寫在最后

雖然沒有具體規定哪個函數應該做什么,但是知道執行順序以后,就很方便我們拓展了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM