首頁 > 軟體

axios攔截器工作方式及原理原始碼解析

2023-02-12 06:00:22

axios 攔截器的設定方式

本文所用 axios 版本號為:1.3.2

axios 中有兩種攔截器:

  • axios.interceptors.request.use(onFulfilled, onRejected, options):設定請求攔截器。
    • onFulfilled 方法在傳送請求前執行,接收 config 物件,返回一個新的 config 物件,可在此方法內修改 config 物件。
    • onRejected 方法在 onFulfilled 執行錯誤後執行,接收 onFulfilled 執行後的錯誤物件。
    • options 設定引數
      • synchronous:控制請求攔截器是否為非同步執行,預設為 true,每個攔截器都可以單獨設定,只要有一個設定為 false 則為同步執行,否則為非同步執行。
      • runWhen:一個方法,接收 config 物件作為引數,在每次呼叫設定的攔截器方法前呼叫,返回結果為 true 時執行攔截器方法。
  • axios.interceptors.response.use(onFulfilled, onRejected, options):設定響應攔截器。
    • onFulfilled 方法在返回響應結果前呼叫,接收響應物件,返回一個新的響應物件,可在此方法內修改響應物件。
    • onRejected 與 options 同 axios.interceptors.request.use
axios.interceptors.request.use(
  function (config) {
    return config;
  },
  function (error) {
    return Promise.reject(error);
  }
);
axios.interceptors.response.use(
  function (response) {
    return response;
  },
  function (error) {
    return Promise.reject(error);
  }
);

可以新增多個攔截器:

axios.interceptors.request.use(
  function (config) {
    throw new Error("999");
    return config;
  },
  function (error) {
    console.log(1, error);
    return Promise.reject(error);
  }
);
axios.interceptors.request.use(
  function (config) {
    throw new Error("888");
    return config;
  },
  function (error) {
    console.log(2, error);
    return Promise.reject(error);
  }
);
axios.interceptors.response.use(
  function (response) {
    return response;
  },
  function (error) {
    console.log(3, error);
    return Promise.reject(error);
  }
);
axios.interceptors.response.use(
  function (response) {
    return response;
  },
  function (error) {
    console.log(4, error);
    return Promise.reject(error);
  }
);
axios.get("https://www.baidwwu.com").catch((error) => {
  console.log(4, error);
});
// 2  Error: 888
// 1  Error: 888
// 3  Error: 888
// 4  Error: 888
// 5  Error: 888

先執行請求攔截器,後執行響應攔截器。

  • 設定多個請求攔截器的情況:後新增的攔截器先執行。
  • 設定多個響應攔截器的情況:按照設定順序執行。

use() 方法的定義

先來看 use() 方法相關程式碼:

this.interceptors = {
  request: new InterceptorManager$1(),
  response: new InterceptorManager$1(),
};
var InterceptorManager = (function () {
  function InterceptorManager() {
    this.handlers = [];
  }
  // ...
  // _createClass 方法可以給物件的原型上新增屬性
  _createClass(InterceptorManager, [
    {
      key: "use",
      value: 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;
      },
    },
    {
      key: "forEach",
      value: function forEach(fn) {
        utils.forEach(this.handlers, function forEachHandler(h) {
          if (h !== null) {
            fn(h);
          }
        });
      },
    },
    // ...
  ]);
  return InterceptorManager;
})();
var InterceptorManager$1 = InterceptorManager;

可以看到 interceptors.requestinterceptors.response 各自指向 InterceptorManager 的範例。

InterceptorManager 原型上設定了 use() 方法,執行後給待執行佇列 this.handlers 中新增一條資料。

這裡利用了 this 的特性來區分作用域:誰呼叫 use() 方法就給誰的 handlers 中新增資料。在呼叫 use() 方法之後,已經將回撥函數按順序新增到了 handlers 陣列中。

forEach() 方法用來遍歷當前作用域 handlers 中不為 null 的元素,在執行攔截器時有用到,詳情見下文。

攔截器如何執行

攔截器是在呼叫了 request() 方法前後執行的,先看相關原始碼:

var Axios = (function () {
  _createClass(Axios, [
    {
      key: "request",
      value: function request(configOrUrl, config) {
        // ...
        // 初始化請求攔截器
        var requestInterceptorChain = [];
        var synchronousRequestInterceptors = true;
        this.interceptors.request.forEach(function unshiftRequestInterceptors(
          interceptor
        ) {
          if (
            typeof interceptor.runWhen === "function" &&
            interceptor.runWhen(config) === false
          ) {
            return;
          }
          synchronousRequestInterceptors =
            synchronousRequestInterceptors && interceptor.synchronous;
          requestInterceptorChain.unshift(
            interceptor.fulfilled,
            interceptor.rejected
          );
        });
        // 初始化響應攔截器
        var responseInterceptorChain = [];
        this.interceptors.response.forEach(function pushResponseInterceptors(
          interceptor
        ) {
          responseInterceptorChain.push(
            interceptor.fulfilled,
            interceptor.rejected
          );
        });
        var promise;
        var i = 0;
        var len;
        // 請求攔截器同步執行模式
        if (!synchronousRequestInterceptors) {
          var chain = [dispatchRequest.bind(this), undefined];
          chain.unshift.apply(chain, requestInterceptorChain);
          chain.push.apply(chain, responseInterceptorChain);
          len = chain.length;
          promise = Promise.resolve(config);
          console.log(11, chain);
          while (i < len) {
            promise = promise.then(chain[i++]).catch(chain[i++]);
          }
          return promise;
        }
        // 請求攔截器非同步執行模式
        len = requestInterceptorChain.length;
        var newConfig = config;
        i = 0;
        while (i < len) {
          var onFulfilled = requestInterceptorChain[i++];
          var onRejected = requestInterceptorChain[i++];
          try {
            newConfig = onFulfilled(newConfig);
          } catch (error) {
            onRejected.call(this, error);
            break;
          }
        }
        try {
          promise = dispatchRequest.call(this, newConfig);
        } catch (error) {
          return Promise.reject(error);
        }
        i = 0;
        len = responseInterceptorChain.length;
        while (i < len) {
          promise = promise.then(
            responseInterceptorChain[i++],
            responseInterceptorChain[i++]
          );
        }
        return promise;
      },
    },
  ]);
})();

上面是相關的全部程式碼,下面進行分解。

攔截器回撥方法的新增順序

var requestInterceptorChain = []; // 請求攔截器執行鏈
// 是否同步執行請求攔截器,每個攔截器都可以單獨設定,但是隻有所有攔截器都設定為true才為true
var synchronousRequestInterceptors = true;
this.interceptors.request.forEach(function unshiftRequestInterceptors(
  interceptor
) {
  // 傳入 runWhen() 方法時,如果 runWhen() 方法返回值為 false 則忽略這個請求攔截器
  if (
    typeof interceptor.runWhen === "function" &&
    interceptor.runWhen(config) === false
  ) {
    return;
  }
  // 是否同步執行
  synchronousRequestInterceptors =
    synchronousRequestInterceptors && interceptor.synchronous;
  // 用 unshift() 新增資料,後設定的攔截器在前
  requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
});
var responseInterceptorChain = []; // 響應攔截器執行鏈
this.interceptors.response.forEach(function pushResponseInterceptors(
  interceptor
) {
  // 響應攔截器按順序加在後面
  responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});

interceptors.request.forEach() 的定義在上文提到過,封裝為遍歷自身的 handlers 陣列,與陣列的 forEach 類似,只是過濾了值為 null 的元素。

runWhen() 方法接收 config,返回 boolean,控制是否將當前攔截器的回撥函數新增到執行鏈中。

請求攔截器用 unshift() 方法新增,所以後設定的先執行,響應攔截器用 push() 方法新增,所以按照設定順序執行。

只要有一個攔截器的 synchronous 設定為 false,則 synchronousRequestInterceptors 的值為 false

同步執行請求攔截器(順序執行)

synchronousRequestInterceptorsfalse 時為同步執行,相關邏輯如下:

var promise;
var i = 0;
var len;
if (!synchronousRequestInterceptors) {
  var chain = [dispatchRequest.bind(this), undefined]; // 執行鏈
  chain.unshift.apply(chain, requestInterceptorChain); // 將請求攔截器新增到請求之前
  chain.push.apply(chain, responseInterceptorChain); // 將響應攔截器新增到響應之後
  len = chain.length;
  promise = Promise.resolve(config);
  while (i < len) {
    promise = promise.then(chain[i++], chain[i++]); // 用 Promise 包裝執行鏈
  }
  return promise;
}

dispatchRequest() 是傳送請求的方法。

同步執行模式下,會將執行鏈中的所有方法用 Promise 進行封裝,前一個方法執行完畢後將其返回值作為引數傳遞給下一個方法。

這裡的 chain 其實就是所有攔截器方法與請求方法合併而成的執行鏈,等價於: [...requestInterceptorChain, dispatchRequest.bind(this), ...responseInterceptorChain]

從一個例子來看 chain 的成員:

axios.interceptors.request.use(onFulfilled1, onRejected1);
axios.interceptors.request.use(onFulfilled2, onRejected2);
axios.interceptors.response.use(onFulfilled3, onRejected3);
axios.interceptors.response.use(onFulfilled4, onRejected4);
axios.get();
// chain: [onFulfilled2, onRejected2, onFulfilled1, onRejected1, dispatchRequest, onFulfilled3, onRejected3, onFulfilled4, onRejected4]

在構建 Promise 鏈的時候,一次遍歷中取了兩個方法傳遞給 then():

promise = Promise.resolve(config);
while (i < len) {
  promise = promise.then(chain[i++], chain[i++]); // 用 Promise 包裝執行鏈
}
return promise;

非同步執行請求攔截器(同時執行)

var promise;
var i = 0;
var len = requestInterceptorChain.length; // 請求執行鏈的長度
var newConfig = config;
i = 0;
// 執行請求攔截器回撥
while (i < len) {
  var onFulfilled = requestInterceptorChain[i++]; // use() 的第一個引數
  var onRejected = requestInterceptorChain[i++]; // use() 的第二個引數
  // 執行成功後繼續執行,執行失敗後停止執行。
  try {
    newConfig = onFulfilled(newConfig);
  } catch (error) {
    onRejected.call(this, error);
    break;
  }
}
// 執行傳送請求方法
try {
  promise = dispatchRequest.call(this, newConfig);
} catch (error) {
  return Promise.reject(error); // 執行失敗後退出
}
// 執行響應攔截器回撥
i = 0;
len = responseInterceptorChain.length;
while (i < len) {
  promise = promise.then(
    responseInterceptorChain[i++], // use() 的第一個引數
    responseInterceptorChain[i++] // use() 的第二個引數
  );
}
return promise;

dispatchRequest() 是傳送請求的方法。

while 迴圈遍歷所有的請求攔截器並呼叫,將執行語句包裹在 try-catch 語句中,只要有一個請求攔截器異常就停止迴圈。

可以看到在非同步模式下,請求攔截器為非同步執行,但是不影響傳送請求,而響應攔截器還是在請求響應後同步執行。

Q&A

攔截器是如何工作的

呼叫 .request.use().response.use() 方法時,將傳入的攔截器回撥方法分別存入 請求攔截器回撥陣列響應攔截器回撥陣列

在呼叫 .get() 等方法時,將 請求攔截器回撥陣列響應攔截器回撥陣列 與傳送請求的方法拼接成一個完整的執行鏈,按照同步或非同步的模式呼叫執行鏈中的方法。

響應攔截器總是在返回響應結果後按順序執行。

請求攔截器根據 synchronous 設定不同,行為有所不同:

  • 非同步執行請求攔截器模式。
  • 沒有設定 synchronous 時預設為非同步執行請求攔截器模式,即遍歷執行所有的請求攔截器一參回撥,執行報錯後停止遍歷,並執行該攔截器的二參回撥。
  • 同步執行請求攔截器模式。
  • 設定 synchronousfalse 時為同步執行請求攔截器模式,將執行鏈包裝成一個 Promise 鏈順序執行。

攔截器的執行順序

先執行請求攔截器,後執行響應攔截器。

  • 設定多個請求攔截器的情況:後新增的攔截器先執行。
  • 設定多個響應攔截器的情況:按照設定順序執行。

同步&非同步

計算機中的同步,指的是現實中的一步一步(同一時間只能幹一件事),非同步指的是同時進行(同一時間能幹多件事)。

參考 npm axios

以上就是axios攔截器工作方式及原理原始碼解析的詳細內容,更多關於axios攔截器工作原理的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com