一比一還原axios原始碼(五)—— 攔截器

Zaking發表於2022-03-17

  上一篇,我們擴充套件了Axios,構建了一個Axios類,然後通過這個Axios工廠類,建立真正的axios例項。那麼今天,我們來實現下Axios的攔截器也就是interceptors。我們來簡單看下Axios的interceptors的API:

 

   首先我們來看,axios上有一個interceptors屬性,該屬性上還有兩個屬性,分別對應request和response,並且都有一個一樣的use方法,該方法目前有兩個引數,分別對應著Promise中的resolve和reject。

 

  另外,你還可以通過對應攔截器的eject方法,移除某個攔截器。

  最後,我們還可以通過配置第三個引數,確定執行攔截器的條件、是否非同步等。最後的最後,我們還需要知道攔截器的執行順序,我們先來看一段程式碼:

axios.interceptors.request.use((config) => {
  config.headers.test += "1";
  return config;
});
axios.interceptors.request.use((config) => {
  config.headers.test += "2";
  return config;
});
axios.interceptors.request.use((config) => {
  config.headers.test += "3";
  return config;
});

axios.interceptors.response.use((res) => {
  res.data += "1";
  return res;
});
let c5 = axios.interceptors.response.use((res) => {
  res.data += "2";
  return res;
});
axios.interceptors.response.use((res) => {
  res.data += "3";
  return res;
});

axios.interceptors.response.eject(c5);

axios({
  url: "/c5/get",
  method: "get",
  headers: {
    test: "",
  },
}).then((res) => {
  console.log(res.data);
});

  這是我們最終demo裡的程式碼,它的結果是什麼樣子呢?我得在這裡就給出大家答案,不然有個核心的點大家可能就不理解了。其中request的header中的tes的值是321,列印的response的結果是13。OK,依照此我們可以得出結論,就是越靠近請求的攔截器越先執行,什麼意思呢?就是我們文件流中寫在後面的請求攔截器最先執行,寫在前面的響應攔截器最先執行。它是一種以中心向外散射的一種模型。

  那麼我們接下來看怎麼來實現這個攔截器吧:

"use strict";

import utils from "./../utils";

function InterceptorManager() {
  this.handlers = [];
}

/**
 * Add a new interceptor to the stack
 *
 * @param {Function} fulfilled The function to handle `then` for a `Promise`
 * @param {Function} rejected The function to handle `reject` for a `Promise`
 *
 * @return {Number} An ID used to remove interceptor later
 */
InterceptorManager.prototype.use = function use(fulfilled, rejected, options) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected,
    synchronous: options ? options.synchronous : false,
    runWhen: options ? options.runWhen : null,
  });
  return this.handlers.length - 1;
};

/**
 * Remove an interceptor from the stack
 *
 * @param {Number} id The ID that was returned by `use`
 */
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null;
  }
};

/**
 * Iterate over all the registered interceptors
 *
 * This method is particularly useful for skipping over any
 * interceptors that may have become `null` calling `eject`.
 *
 * @param {Function} fn The function to call for each interceptor
 */
InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
};

export default InterceptorManager;

  首先,我們在core資料夾下建立一個InterceptorManager.js,程式碼如上,在檔案內我們構建一個InterceptorManager類,這個類上只有一個陣列作為儲存具體攔截器的容器。

  然後呢,我們在它的原型上掛載一個use方法,這個前面說過了,就是要把具體的攔截器放置到容器內,以待最後的使用,其中放置的是一個包含了resolve和reject函式以及兩個引數的物件,這個方法返回了一個對應攔截器在容器內的下標作為id。

  再然後呢,就是一個eject方法,使用use方法中返回的下標,直接設定為null即可,提問!為啥這裡不直接移除(splice啥的)容器內的攔截器,而是把對應位置的攔截器設定為null呢?

  最後,我們提供一個forEach方法,迴圈執行容器內的攔截器即可。那麼到現在為止,整個攔截器管理類就實現了。下面我們看看如何使用。

Axios.prototype.request = function (configOrUrl, config) {
  if (typeof configOrUrl === "string") {
    if (!config) {
      config = {};
    }
    config.url = configOrUrl;
  } else {
    config = configOrUrl;
  }
  // 請求攔截器呼叫鏈
  var requestInterceptorChain = [];
  // 是否同步
  var synchronousRequestInterceptors = true;
  // 通過攔截器的forEach方法,通過回撥函式的方式,把所有的請求攔截放到requestInterceptorChain陣列裡
  this.interceptors.request.forEach(function unshiftRequestInterceptors(
    interceptor
  ) {
    if (
      // 判斷下如果runWhen是false就return掉了
      typeof interceptor.runWhen === "function" &&
      interceptor.runWhen(config) === false
    ) {
      return;
    }
    // 判斷是否是同步執行
    synchronousRequestInterceptors =
      synchronousRequestInterceptors && interceptor.synchronous;
    // 把兩個回撥函式放到陣列的頭部
    // 注意這裡不是unshift一個陣列,而是獨立的,就是這樣[interceptor.fulfilled,interceptor.rejected]
    // [3,2,1]
    requestInterceptorChain.unshift(
      interceptor.fulfilled,
      interceptor.rejected
    );
  });
  // 響應攔截器呼叫鏈
  var responseInterceptorChain = [];
  // response這個比較簡單,直接push進陣列就完事了
  this.interceptors.response.forEach(function pushResponseInterceptors(
    interceptor
  ) {
    responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
  });
  // 定一個promise變數,後面用
  var promise;
  // 如果不是同步的
  if (!synchronousRequestInterceptors) {
    var chain = [dispatchRequest, undefined];
    // 這塊呢,就把整個requestInterceptorChain放到chain的前面
    Array.prototype.unshift.apply(chain, requestInterceptorChain);
    // 這個就是把responseInterceptorChain放到[requestInterceptorChain,chain]後面
    chain = chain.concat(responseInterceptorChain);
    // 額外要說的是到了這裡,這個chain陣列是什麼樣的呢
    // 我們列印下,以我們之前的例子程式碼為例:
    // 它實際上是這樣的[fn,undefined,fn,undefined,fn,undefined,fn,undefined,fn,undefined,fn,undefined]
    // 具體點,[requestInterceptorChain,chain,responseInterceptorChain]
    // 再具體點:[requestResolve3,undefined,requestResolve2,undefined,requestResolve1,undefined,dispatchRequest, undefined,responseResolve1,undefined,responseResolve3,undefined]
    console.log(chain, "chian");
    // 這塊可能就優點疑惑了,首先promise變數變成了一個已經resolved的Promise,resolve出去的就是config配置
    promise = Promise.resolve(config);
    while (chain.length) {
      // 所以這裡的then裡面就是這樣(resolve,reject)
      // 注意then方法的第二個引數就是reject的。
      // 換句話說,這裡就形成了一個一個的鏈式呼叫,源頭是一個已經resolved的promise。
      promise = promise.then(chain.shift(), chain.shift());
    }
    // 返回咯
    return promise;
  }
  // 那如果是同步的話,走下面的程式碼
  // 很簡單,就是同步執行罷了,我就不說了哦。
  var newConfig = config;
  while (requestInterceptorChain.length) {
    var onFulfilled = requestInterceptorChain.shift();
    var onRejected = requestInterceptorChain.shift();
    try {
      // 新的config就是onFulfilled同步函式執行的結果,一步一步往下傳
      newConfig = onFulfilled(newConfig);
    } catch (error) {
      onRejected(error);
      break;
    }
  }
  // 執行dispatchRequest返回個promise,dispatchRequest本身就會返回promise,對吧?
  try {
    promise = dispatchRequest(newConfig);
  } catch (error) {
    return Promise.reject(error);
  }
  // 迴圈執行responseInterceptorChain鏈。
  while (responseInterceptorChain.length) {
    promise = promise.then(
      responseInterceptorChain.shift(),
      responseInterceptorChain.shift()
    );
  }
  // 返回,結束
  return promise;
};

  上面是完整的request方法的註釋,還算清晰,大家也可以去gitHub上檢視。那,簡單回顧下,整個執行的核心其實分為了同步和非同步,但是其實整體的程式碼都不復雜,就是呼叫的時候會稍微繞一點。requestInterceptorChain通過unshift後新增的就變成的陣列的頭部,先新增的就變成了陣列的尾部。通過while迴圈,每次都shift出去對應的回撥函式並執行返回promise,這是非同步的做法,同步的做法就比較簡單,同步執行requestInterceptorChain,然後在呼叫request的時候,返回promise,包括後面的responseInterceptorChain也是promise,因為最後要丟擲promise供axios例項使用。

  好了,今天的邏輯稍微複雜些,但是本身並不是很難,例子已經在gitHub上了,大家可以親自去體驗下。

相關文章