Axios 的簡單概述以及它的一個核心功能攔截器的詳解

一隻程式猿小白發表於2020-10-22

一、概述

Axios 是一個基於 Promise 的 HTTP 客戶端,可以用在瀏覽器和 node.js 中,本質是XMLHttpRequests請求即ajax請求。

擁有以下特性:   

從瀏覽器中建立 XMLHttpRequests

從 node.js 建立 http 請求

支援Promise API;

能夠攔截請求和響應;

能夠轉換請求和響應資料;

能夠取消請求;

能夠自定轉化JSON資料;

客戶端支援防禦CSRF(XSRF)

Axios 支援大多數主流的瀏覽器,比如 Chrome、Firefox、Safari 和 IE 8以上。

詳細可訪問axios中文網::http://www.axios-js.com/zh-cn/docs/

二、HTTP 攔截器的設計與實現

2.1 需求分析

比如通常會使用 token 進行使用者的身份認證。這就要求在認證通過後,我們需要在每個請求上都攜帶認證資訊。針對這個需求,為了避免為每個請求單獨處理,所以一般通過封裝統一的 request 函式來為每個請求統一新增 token 資訊。

但是如果響應進行統一處理的話,會越來越難維護,所以希望在接收到響應之後做一些額外的邏輯

因此,Axios 為我們提供瞭解決方案 —— 攔截器,作用如下:

請求攔截器:該類攔截器的作用是在請求傳送前統一執行某些操作,比如在請求頭中新增 token 欄位。

響應攔截器:該類攔截器的作用是在接收到伺服器響應後統一執行某些操作,比如發現響應狀態碼為 401 時,自動跳轉到登入頁。

攔截器的使用方式如下:

在 axios 物件上有一個 interceptors 物件屬性,該屬性又有 request 和 response 2 個屬性, axios.interceptors.request和 axios.interceptors.response 物件提供了 use 方法,就可以分別設定請求攔截器和響應攔截器:(use 方法支援 2 個引數,第一個引數類似 Promise 的 resolve 函式,第二個引數類似 Promise 的 reject 函式。我們可以在 resolve 函式和 reject 函式中執行同步程式碼或者是非同步程式碼邏輯。)

// 新增一個請求攔截器
axios.interceptors.request.use(function (config){
    // 在傳送請求之前可以做一些事情
     config.headers.token = 'added by interceptor';    //在headers中攜帶token
     return config;
},function (error){
    // 處理請求錯誤
    return Promise.reject(error)
 });

// 新增一個響應攔截器
axios.interceptors.response.use(function (res){
    // 在傳送請求之前可以做一些事情
     return res;
},function (error){
    // 處理請求錯誤
    return Promise.reject(error)
 });

我們先來分析一下攔截器的設計思路。Axios 的作用是用於傳送 HTTP 請求,而請求攔截器和響應攔截器的本質都是一個實現特定功能的函式。

我們可以按照功能把傳送 HTTP 請求拆解成不同型別的子任務,比如有用於處理請求配置物件的子任務,用於傳送 HTTP 請求的子任務和用於處理響應物件的子任務。當我們按照指定的順序來執行這些子任務時,就可以完成一次完整的 HTTP 請求。

瞭解完這些,接下來我們將從 任務註冊、任務編排和任務排程 三個方面來分析 Axios 攔截器的實現。

2.2 任務註冊

通過前面攔截器的使用示例,我們已經知道如何註冊請求攔截器和響應攔截器,其中請求攔截器用於處理請求配置物件的子任務,而響應攔截器用於處理響應物件的子任務。要搞清楚任務是如何註冊的,就需要了解 axiosaxios.interceptors 物件。

// lib/axios.js
function createInstance(defaultConfig) {
  var context = new Axios(defaultConfig);
  var instance = bind(Axios.prototype.request, context);

  // Copy axios.prototype to instance
  utils.extend(instance, Axios.prototype, context);
  // Copy context to instance
  utils.extend(instance, context);
  return instance;
}

// Create the default instance to be exported
var axios = createInstance(defaults);

在 Axios 的原始碼中,我們找到了 axios 物件的定義,很明顯預設的 axios 例項是通過 createInstance 方法建立的,該方法最終返回的是 Axios.prototype.request 函式物件。同時,我們發現了 Axios 的建構函式:

// lib/core/Axios.js
function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

在建構函式中,我們找到了 axios.interceptors 物件的定義,也知道了 interceptors.requestinterceptors.response 物件都是 InterceptorManager 類的例項。因此接下來,進一步分析 InterceptorManager 建構函式及相關的 use 方法就可以知道任務是如何註冊的:

// lib/core/InterceptorManager.js
function InterceptorManager() {
  this.handlers = [];
}

InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  // 返回當前的索引,用於移除已註冊的攔截器
  return this.handlers.length - 1;
};

通過觀察 use 方法,我們可知註冊的攔截器都會被儲存到 InterceptorManager 物件的 handlers 屬性中。下面我們用一張圖來總結一下 Axios 物件與 InterceptorManager 物件的內部結構與關係:

2.3 任務編排

現在我們已經知道如何註冊攔截器任務,但僅僅註冊任務是不夠,我們還需要對已註冊的任務進行編排,這樣才能確保任務的執行順序。這裡我們把完成一次完整的 HTTP 請求分為處理請求配置物件、發起 HTTP 請求和處理響應物件 3 個階段。

接下來我們來看一下 Axios 如何發請求的:

axios({
  url: '/hello',
  method: 'get',
}).then(res =>{
  console.log('axios res: ', res)
  console.log('axios res.data: ', res.data)
})

通過前面的分析,我們已經知道 axios 物件對應的是 Axios.prototype.request 函式物件,該函式的具體實現如下:

// lib/core/Axios.js
Axios.prototype.request = function request(config) {
  config = mergeConfig(this.defaults, config);

  // 省略部分程式碼
  var chain = [dispatchRequest, undefined];
  var promise = Promise.resolve(config);

  // 任務編排
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  // 任務排程
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

任務編排的程式碼比較簡單,我們來看一下任務編排前和任務編排後的對比圖:

2.4 任務排程

任務編排完成後,要發起 HTTP 請求,我們還需要按編排後的順序執行任務排程。在 Axios 中具體的排程方式很簡單,具體如下所示:

 // lib/core/Axios.js
Axios.prototype.request = function request(config) {
  // 省略部分程式碼
  var promise = Promise.resolve(config);
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }
}

因為 chain 是陣列,所以通過 while 語句我們就可以不斷地取出設定的任務,然後組裝成 Promise 呼叫鏈從而實現任務排程,對應的處理流程如下圖所示:

下面我們來回顧一下 Axios 攔截器完整的使用流程:

// 新增請求攔截器 —— 處理請求配置物件
axios.interceptors.request.use(function (config) {
  config.headers.token = 'added by interceptor';
  return config;
});

// 新增響應攔截器 —— 處理響應物件
axios.interceptors.response.use(function (data) {
  data.data = data.data + ' - modified by interceptor';
  return data;
});

axios({
  url: '/hello',
  method: 'get',
}).then(res =>{
  console.log('axios res.data: ', res.data)
})

介紹完 Axios 的攔截器,我們來總結一下它的優點。Axios 通過提供攔截器機制,讓開發者可以很容易在請求的生命週期中自定義不同的處理邏輯。此外,也可以通過攔截器機制來靈活地擴充套件 Axios 的功能,比如 Axios 生態中列舉的 axios-response-loggeraxios-debug-log 這兩個庫。

參考 Axios 攔截器的設計模型,我們就可以抽出以下通用的任務處理模型:

 

 

 

相關文章