基於原生fetch封裝一個帶有攔截器功能的fetch,類似axios的攔截器

混沌傳奇發表於2019-02-24

背景

為什麼要給fetch增加攔截器?瀏覽器提供的fetch方法不夠用嗎?

是的。原生fetch確實不夠用。

在專案中,如果想要在所有的網路請求之前往header中加入許可權資訊(比如:authorization=xxxx)。用原生fetch的話,你只能在每個fetch請求的時候,header配置中寫上authorization=xxxx。或者在請求結果返回來之後,對響應結果做一些特殊處理,在原生fetch中,我們只能在每個請求的結果回來之後,都寫一遍特殊處理。這樣做起來一點都不優雅,一點都不高階。相信每一個程式設計師都是一個懶人,能用少量程式碼實現出更優雅,更健壯程式的話,絕對不用大量程式碼實現一個很脆弱的程式。

用過axios的人,都知道axios有攔截器功能(axios.interceptors)。遇到需要在所有ajax請求之前或者請求完成之後做一些事情的話,我們就可以在攔截器中寫。攔截器可以在發起請求之前攔截請求,對請求做一些處理,然後,再繼續請求;也可以在請求完成之後攔截請求,對響應結果做一些處理,然後再把結果返回。

這樣做的好處是我們不需要在每個請求的時候,都去寫相同的這些程式碼,可以把全域性生效/通用的處理放在攔截器中來實現。可以省去很多程式碼,也避免了我們在開發時,不小心某一個請求漏掉了做全域性處理。

攔截器設計思考

  1. 目的是在原生fetch基礎之上增加攔截請求的功能。
  2. 封裝後,最終暴露在外面的介面應該於原生fetch的使用方法一致,同時增加攔截器功能
  3. 攔截器的API設計參考axios的攔截器(個人比較喜歡axios的攔截器設計)。

攔截器API使用方法

你可以在then和catch之前攔截請求和響應。

// 新增一個請求攔截器
c_fetch.interceptors.request.use(function (config) {
    // Do something before request is sent
    return config;
  });

// 新增一個響應攔截器
c_fetch.interceptors.response.use(function (response) {
    // Do something with response data
    return response;
  });
複製程式碼

攔截器實現方案

  1. 為了保證封裝後的fetch暴露在最外面的api跟原生fetch保持一致。所以,我們要暴露到最外面的api應該是個跟fetch接收相同引數的函式。
    我們看下原生fetch方法語法解釋和接收引數解釋。
語法: 
Promise<Response> fetch(input[, init]); 
複製程式碼
引數:
?input
定義要獲取的資源。這可能是:
一個 USVString 字串,包含要獲取資源的 URL。一些瀏覽器會接受 blob: 和 data: 作為 schemes.
一個 Request 物件。

init 可選
一個配置項物件,包括所有對請求的設定。可選的引數有:
method: 請求使用的方法,如 GET、POST。
headers: 請求的頭資訊,形式為 Headers 的物件或包含 ByteString 值的物件字面量。
body: 請求的 body 資訊:可能是一個 Blob、BufferSource、FormData、URLSearchParams 或者 USVString 物件。注意 GET 或 HEAD 方法的請求不能包含 body 資訊。
mode: 請求的模式,如 cors、 no-cors 或者 same-origin。
credentials: 請求的 credentials,如 omit、same-origin 或者 include。為了在當前域名內自動傳送 cookie , 必須提供這個選項, 從 Chrome 50 開始, 這個屬性也可以接受 FederatedCredential 例項或是一個 PasswordCredential 例項。
cache:  請求的 cache 模式: default 、 no-store 、 reload 、 no-cache 、 force-cache 或者 only-if-cached 。
redirect: 可用的 redirect 模式: follow (自動重定向), error (如果產生重定向將自動終止並且丟擲一個錯誤), 或者 manual (手動處理重定向). 在Chrome中,Chrome 47之前的預設值是 follow,從 Chrome 47開始是 manual。
referrer: 一個 USVString 可以是 no-referrer、client或一個 URL。預設是 client。
referrerPolicy: Specifies the value of the referer HTTP header. May be one of no-referrer、 no-referrer-when-downgrade、 origin、 origin-when-cross-origin、 unsafe-url 。
integrity: 包括請求的  subresource integrity 值 ( 例如: sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=)。
複製程式碼

原生fetch的詳細文件請移步MDN查閱:
developer.mozilla.org/zh-CN/docs/…

從上面的fetch的語法解釋和引數解釋,我們可以知道fetch是個函式;接收兩個引數,第一個引數定義要獲取的資源,第二個引數為可選項,一個配置項物件,包括所有對請求的設定;fetch函式分返回結果是一個promise物件。

  1. 我們先來實現封裝後,暴露出來的c_fetch函式。
  function c_fetch (input, init = {}) {
    //fetch預設請求方式設為GET
    if(!init.method){
      init.method = 'GET'
    }
    
    //interceptors_req是攔截請求的攔截處理函式集合
    //後面會講解interceptors_req的定義與實現
    interceptors_req.forEach(interceptors => {
      init = interceptors(init);
    })
    
    //在原生fetch外面封裝一個promise,為了在promise裡面可以對fetch請求的結果做攔截處理。
    //同時,保證c_fetch函式返回的結果是個promise物件。
    return new Promise(function (resolve, reject) {
      //發起fetch請求,fetch請求的形參是接收上層函式的形參
      fetch(input, init).then(res => {
        //interceptors_res是攔截響應結果的攔截處理函式集合
        //後面會講解interceptors_res的定義與實現
        interceptors_res.forEach(interceptors => {
          //攔截器對響應結果做處理,把處理後的結果返回給響應結果。
          res = interceptors(res);
        })
        //將攔截器處理後的響應結果resolve出去
        resolve(res)
      }).catch(err => {
        reject(err);
      })
    })

  }
複製程式碼
  1. c_fetch 函式實現完了,我們就差攔截器的實現了。我們在 c_fetch 函式的基礎上增加interceptors,用來註冊攔截器。
  //定義用來儲存攔截請求和攔截響應結果的處理函式集合
  let interceptors_req = [], interceptors_res = [];

  function c_fetch (input, init = {}) {
    //fetch預設請求方式設為GET
    if(!init.method){
      init.method = 'GET'
    }
    
    //interceptors_req是攔截請求的攔截處理函式集合
    interceptors_req.forEach(interceptors => {
      init = interceptors(init);
    })
    
    //在原生fetch外面封裝一個promise,為了在promise裡面可以對fetch請求的結果做攔截處理。
    //同時,保證c_fetch函式返回的結果是個promise物件。
    return new Promise(function (resolve, reject) {
      //發起fetch請求,fetch請求的形參是接收上層函式的形參
      fetch(input, init).then(res => {
        //interceptors_res是攔截響應結果的攔截處理函式集合
        interceptors_res.forEach(interceptors => {
          //攔截器對響應結果做處理,把處理後的結果返回給響應結果。
          res = interceptors(res);
        })
        //將攔截器處理後的響應結果resolve出去
        resolve(res)
      }).catch(err => {
        reject(err);
      })
    })

  }
  
  //在c_fetch函式上面增加攔截器interceptors,攔截器提供request和response兩種攔截器功能。
  //可以通過request和response的use方法來繫結兩種攔截器的處理函式。
  //use方法接收一個引數,引數為一個callback函式,callback函式用來作為攔截器的處理函式;
  //request.use方法會把callback放在interceptors_req中,等待執行。
  //response.use方法會把callback放在interceptors_res中,等待執行。
  //攔截器的處理函式callback接收一個引數。
  //request攔截器的callback接收的是請求發起前的config;
  //response攔截器的callback接收的是網路請求的response結果。
  c_fetch.interceptors = {
    request: {
      use: function (callback) {
        interceptors_req.push(callback);
      }
    },
    response: {
      use: function (callback) {
        interceptors_res.push(callback);
      }
    }
  }
複製程式碼
  1. 最後將整個封裝之後,含有攔截器功能的fetch包裝為一個外掛暴露給開發者使用。
/**
 * c_fetch
 * 基於原生fetch封裝了攔截器功能,暴露出來的c_fetch跟原生fetch用法一致,只是增加了攔截器功能。攔截器用法參考axios的攔截器用法。
 * 攔截器: c_fetch.interceptors
 * 注意: 攔截器不攔截reject型別的response結果
 */

  //定義用來儲存攔截請求和攔截響應結果的處理函式集合
  let interceptors_req = [], interceptors_res = [];

  function c_fetch (input, init = {}) {
    //fetch預設請求方式設為GET
    if(!init.method){
      init.method = 'GET'
    }
    
    //interceptors_req是攔截請求的攔截處理函式集合
    interceptors_req.forEach(interceptors => {
      init = interceptors(init);
    })
    
    //在原生fetch外面封裝一個promise,為了在promise裡面可以對fetch請求的結果做攔截處理。
    //同時,保證c_fetch函式返回的結果是個promise物件。
    return new Promise(function (resolve, reject) {
      //發起fetch請求,fetch請求的形參是接收上層函式的形參
      fetch(input, init).then(res => {
        //interceptors_res是攔截響應結果的攔截處理函式集合
        interceptors_res.forEach(interceptors => {
          //攔截器對響應結果做處理,把處理後的結果返回給響應結果。
          res = interceptors(res);
        })
        //將攔截器處理後的響應結果resolve出去
        resolve(res)
      }).catch(err => {
        reject(err);
      })
    })

  }
  
  //在c_fetch函式上面增加攔截器interceptors,攔截器提供request和response兩種攔截器功能。
  //可以通過request和response的use方法來繫結兩種攔截器的處理函式。
  //use方法接收一個引數,引數為一個callback函式,callback函式用來作為攔截器的處理函式;
  //request.use方法會把callback放在interceptors_req中,等待執行。
  //response.use方法會把callback放在interceptors_res中,等待執行。
  //攔截器的處理函式callback接收一個引數。
  //request攔截器的callback接收的是請求發起前的config;
  //response攔截器的callback接收的是網路請求的response結果。
  c_fetch.interceptors = {
    request: {
      use: function (callback) {
        interceptors_req.push(callback);
      }
    },
    response: {
      use: function (callback) {
        interceptors_res.push(callback);
      }
    }
  }

  export default c_fetch;
複製程式碼

總結

本篇文章只是實現了一個最基本的攔截器功能,文章字數有限,沒有深入講解更加成熟的攔截器實現方式。有興趣的朋友可以閱讀下axios的攔截器實現,很有意思的。axios的攔截器會更加完善。

我這裡針對fetch的封裝也只是最基本的封裝,目的是講解攔截器的實現,沒有過於複雜化。上文中暴露出來的c_fetch其實可以在封裝一層cc_fetch,用bind方法把c_fetch的方法繫結在cc_fetch上,最後暴露出來的是cc_fetch。這樣做的好處是保護了c_fetch的方法不會被外部所影響,篡改等。當然了,這只是我個人的一些看法,不代表所有人。

最後謝謝各位能夠堅持閱讀到最後,希望您閱讀本篇文章能夠有所收穫。謝謝?~

相關文章