ÿÿ深入淺出 axios(一):é è¨­ axios 物件ã€Axios é¡žåˆ¥ã€æ””截器 | Alex Liu

深入淺出 axios(一):é è¨­ axios 物件ã€Axios é¡žåˆ¥ã€æ””截器

• 19 min read

axios 是一個 Promise based çš„ HTTP 請求工具,他å¯ä»¥é‹è¡Œåœ¨ã€Œç€è¦½å™¨ç’°å¢ƒã€èˆ‡ã€ŒNode.jsã€ä¸­ã€‚相信在 AJAX 技術被廣泛應用的今日,ç¨å¾®æœ‰ä¸€é»žç¶“é©—çš„æ§æ²¹é–€å°ä»–一定都ä¸é™Œç”Ÿã€‚å› æ­¤é€™ç³»åˆ—åˆ†äº«ä¸æœƒç‰¹åˆ¥è‘—é‡åœ¨å¦‚何使用 axios,而是é‡å°å¹¾å€‹æˆ‘覺得 axios 有趣ã€å¥½ç”¨çš„åœ°æ–¹ï¼Œç ”ç©¶ä»–çš„åŽŸå§‹ç¢¼æ˜¯å¦‚ä½•æ’°å¯«çš„ï¼Œå¾žä¸­å¸æ”¶å¯¶è²´çš„經驗。那就讓我們一起看下去å§ï¼

å‰è¨€

本篇的 axios 版本為 0.21.0

這是一個系列的分享,é è¨ˆæœƒæœ‰å…©ç¯‡ï¼Œæœ¬æ–‡æ˜¯è©²ç³»åˆ—的第一篇。在本文當中會æåˆ°ä»¥ä¸‹é€™äº›å…§å®¹ï¼š

  • é è¨­å°Žå…¥çš„ axios 設計。
  • Axios 類別(Class)設計。
  • 攔截器類別(InterceptorManager Class)設計。

axios 坿‡‰ç”¨åœ¨ã€Œç€è¦½å™¨ç’°å¢ƒã€èˆ‡ã€ŒNode.jsã€ç’°å¢ƒä¸­ã€‚在ç€è¦½å™¨ç’°å¢ƒä¸‹ä½¿ç”¨äº† XMLHttpRequest 而在 Node.js 環境則使用了 http 模組。由於目å‰å·¥ä½œä¸Šçš„使用經驗還是以ç€è¦½å™¨ç«¯ç‚ºä¸»ï¼Œå› æ­¤æœ¬ç³»åˆ—æš«æ™‚ä¹Ÿåªæœƒé‡å°ç€è¦½å™¨ç«¯çš„功能åšç ”究,分享。

在開始先å°å°æä¸€ä¸‹ï¼Œè‡ªå¾žå­¸ç¿’了 TypeScript 後,在使用一套工具éŽç¨‹ä¸­ï¼Œæˆ‘æœƒå¾ˆç¿’æ…£ä¸æ–·åœ°ç¢ºèªé€™å€‹å·¥å…·æä¾›çš„ interface 有哪些(如果有æä¾›çš„話)並æ­é…文件使用。

axios 請求æµç¨‹

一開始,先來看看當é€éŽ axios 發出請求(request)到å–得到資料(response)的éŽç¨‹ä¸­ç™¼ç”Ÿäº†é‚£äº›äº‹æƒ…,以下是我自製的 axios 請求æµç¨‹åœ–:

axios 請求æµç¨‹åœ– - by Alex Liu

從æµç¨‹åœ–å¯ä»¥çŸ¥é“,當é€éŽ axios 發出一個請求後,會先經éŽè«‹æ±‚攔截器(Interceptors),之後ä¾ç…§åŸ·è¡Œç’°å¢ƒé¸æ“‡é©ç•¶çš„請求é©é…器(adapter,介é¢ï¼‰ç™¼å‡ºè«‹æ±‚。å–得請求的回應後,經éŽè™•ç†å›žæ‡‰çš„æ””æˆªå™¨ï¼Œæœ€å¾Œå›žå‚³çµ¦ä½¿ç”¨è€…ï¼Œå®Œæˆæ•´å€‹ HTTP 請求。

é è¨­å°Žå…¥çš„ axios 設計

è¦çž­è§£ axios æä¾›äº†å“ªäº›æ–¹æ³•(methods)與屬性(properties),我們å¯ä»¥å…ˆé€éŽ axios 官方æä¾›çš„ interface 快速ç€è¦½ï¼š

export interface AxiosInstance {
  (config: AxiosRequestConfig): AxiosPromise;
  (url: string, config?: AxiosRequestConfig): AxiosPromise;
  defaults: AxiosRequestConfig;
  interceptors: {
    request: AxiosInterceptorManager<AxiosRequestConfig>;
    response: AxiosInterceptorManager<AxiosResponse>;
  };
  getUri(config?: AxiosRequestConfig): string;
  request<T = any, R = AxiosResponse<T>> (config: AxiosRequestConfig): Promise<R>;
  get<T = any, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig): Promise<R>;
  delete<T = any, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig): Promise<R>;
  head<T = any, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig): Promise<R>;
  options<T = any, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig): Promise<R>;
  post<T = any, R = AxiosResponse<T>>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R>;
  put<T = any, R = AxiosResponse<T>>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R>;
  patch<T = any, R = AxiosResponse<T>>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R>;
}

export interface AxiosStatic extends AxiosInstance {
  create(config?: AxiosRequestConfig): AxiosInstance;
  Cancel: CancelStatic;
  CancelToken: CancelTokenStatic;
  isCancel(value: any): boolean;
  all<T>(values: (T | Promise<T>)[]): Promise<T[]>;
  spread<T, R>(callback: (...args: T[]) => R): (array: T[]) => R;
  isAxiosError(payload: any): payload is AxiosError;
}

declare const axios: AxiosStatic;

當我們使用 import axios from 'axios' 時,此時的 axios 型別為 AxiosStatic。之後如果是é€éŽ axios.create() å–得的回傳值,型別則會是 AxiosInstance。

這裡å¯ä»¥ç™¼ç¾ï¼Œå¦‚果是é€éŽ axios.create() å»ºç«‹çš„å¯¦ä¾‹ï¼Œå°±ä¸æœƒæœ‰åƒæ˜¯ create()ã€isCancel()〠isAxiosError() 諸如此類的方法å¯ä»¥ç”¨ï¼Œä¹Ÿä¸èƒ½å–å¾— Cancelã€CancelToken 等屬性。

createInstance

我們知é“,ä¸è«–是é è¨­å°Žå…¥çš„ axios 或是 axios.create() 的回傳值都å¯ä»¥ç›´æŽ¥ç”¨ä¾†ç™¼é€ HTTP 請求,åƒé€™æ¨£ï¼š

axios({/** config */})

// 或

axios('url', {/** config */})

而這部分在原始碼的部分是這樣設計的:

var bind = require('./helpers/bind');
var Axios = require('./core/Axios');
var mergeConfig = require('./core/mergeConfig');
var defaults = require('./defaults');

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

  return instance;
}

var axios = createInstance(defaults);

axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

// ç•¥
// 這個地方會å†å°‡ä¸€äº›æ–¹æ³•或屬性掛到è¦å°Žå‡º axios 上。
// 這裡也是é è¨­å°Žå…¥çš„ `axios` 與 `axios.create()` 的回傳值型別會ä¸å¤ªä¸€æ¨£çš„原因。

module.exports.default = axios;

我們å¯ä»¥çœ‹åˆ°ï¼Œä¸è«–是é è¨­å°Žå…¥çš„ axios 或是 axios.create() 的回傳值,他們都是由 createInstance() 這個 function 回傳的 function。

在 createInstance() 中,首先會建立一個 Axios 類別的實例(contextï¼‰ï¼Œä½†ä¸æ˜¯ç›´æŽ¥å°‡é€™å€‹å¯¦ä¾‹å›žå‚³å‡ºä¾†ï¼Œè€Œæ˜¯å›žå‚³äº†ä¸€å€‹è®Šæ•¸ï¼ˆinstance),這個 instance 存了一個ç¶å®šäº†ä»¥ context 為 this çš„ Axios 原型上的 request 方法。

說起來很饒å£ï¼Œä½†å°±æ˜¯æ¯ç•¶å‘¼å« axios({ /** config */ }) æ™‚ï¼ŒåŸ·è¡Œçš„å…¶ç­‰åŒæ–¼åŸ·è¡Œï¼š

Axios.prototype.request.bind(context)({ /** config */ })

所以我們知é“,ä¸è«–是é è¨­å°Žå…¥çš„ axios 或是 axios.create() 回傳的都是一個 request çš„ function。ä¸éŽé™¤æ­¤ä¹‹å¤–我們還å¯ä»¥é€™æ¨£ä½¿ç”¨ï¼š

axios.request({ /** config */ })

axios.delete('url', { /** config */ })
axios.get('url', { /** config */ })
axios.head('url', { /** config */ })
axios.options('url', { /** config */ })

axios.post('url', { /** data */ }, { /** config */ })
axios.put('url', { /** data */ }, { /** config */ })
axios.patch('url', { /** data */ }, { /** config */ })

é€™åˆæ˜¯æ€Žéº¼åšåˆ°çš„呢?因為在 JavaScript 的世界中,function 其實也是一個物件,所以就算是 function 也å¯ä»¥ç”¨ç‰©ä»¶çš„æ–¹å¼å­˜å–屬性與其他方法,而在 createInstance 裡é¢é‚„åšäº†å…©ä»¶äº‹æƒ…:

var utils = require('./utils');

function createInstance(defaultConfig) {
  // ç•¥

  utils.extend(instance, Axios.prototype, context);
  utils.extend(instance, context);

  return instance;
}
// lib/utils.js

/**
 * Extends object a by mutably adding to it the properties of object b.
 *
 * @param {Object} a The object to be extended
 * @param {Object} b The object to copy properties from
 * @param {Object} thisArg The object to bind function to
 * @return {Object} The resulting value of object a
 */
function extend(a, b, thisArg) {
  forEach(b, function assignValue(val, key) {
    if (thisArg && typeof val === 'function') {
      a[key] = bind(val, thisArg);
    } else {
      a[key] = val;
    }
  });
  return a;
}

module.exports = {
  // ç•¥
  extend: extend
}

由上é¢çš„原始碼å¯è¦‹ï¼Œextend 會將 b 物件上有的方法或屬性複製到 a 物件上,如果是複製方法,則還需è¦å‚³å…¥ thisArg。

了解 extend 後就å¯ä»¥è§£é‡‹äº†ã€‚首先,先將 Axios 類別原型上的方法複製到 instance 上,並ç¶å®š this 為 context。å†ä¾†å°‡ context 上的屬性也複製到 instance 上。因此回傳的 instance 除了å¯ä»¥ç•¶ function 使用外,也å¯ä»¥å­˜å–到 createInstance 中建立的 context 上的屬性與方法。

補充:mergeConfig 是 axios 中åˆä½µé è¨­ config 與傳入的 config 的方法。而 utils 囊括了å„ç¨®å¥½ç”¨çš„å° function。

Axios 類別(Class)設計

å†ä¾†è¦é€²åˆ° Axios 類別的部分。

é€éŽå®˜æ–¹æä¾›çš„ interface 我們å¯ä»¥å¾—知,Axios 類別原始 interface 大致上長這樣:

// 官方並沒有é‡å° Axios 類別æä¾› interface

interface Axios {
  defaults: AxiosRequestConfig;
  interceptors: {
    request: AxiosInterceptorManager<AxiosRequestConfig>;
    response: AxiosInterceptorManager<AxiosResponse>;
  };
  getUri(config?: AxiosRequestConfig): string;
  request<T = any, R = AxiosResponse<T>> (config: AxiosRequestConfig): Promise<R>;
  get<T = any, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig): Promise<R>;
  delete<T = any, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig): Promise<R>;
  head<T = any, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig): Promise<R>;
  options<T = any, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig): Promise<R>;
  post<T = any, R = AxiosResponse<T>>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R>;
  put<T = any, R = AxiosResponse<T>>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R>;
  patch<T = any, R = AxiosResponse<T>>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R>;
}

屬性

Axios 類別生æˆçš„屬性有兩個:

  • defaults:
    • 型別:Object
    • 該 axios 實例的é è¨­ config,也就是建構時傳入的 instanceConfig。
  • interceptors
    • 型別:Object
    • 該 axios 實例的 request 與 response 攔截器。

建構函å¼åŽŸå§‹ç¢¼å¦‚ä¸‹ï¼š

var InterceptorManager = require('./InterceptorManager');

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

å¯ä»¥çœ‹åˆ°ï¼ŒAxios çš„æ””æˆªå™¨æ˜¯ç”±ä¸€å€‹å« InterceptorManager 的類別建立的,它æä¾›äº†ä¸€äº›é–“單的æ“作來新增ã€åˆªé™¤æ””截器,細節會在下個部分會詳細探究。

方法

類別方法部分,包å«äº†ä¸€å€‹ request 以åŠå…¶ä»–與 HTTP request methods å°å¯«åŒå的方法,外加一個 getUri。接著來看看 request åšäº†å“ªäº›äº‹æƒ…å§ï¼

發出請求 axios.request()

var dispatchRequest = require('./dispatchRequest');

// ç•¥

Axios.prototype.request = function request(xÇ<config) {
  if (typeof config === 'string') {
    config = arguments[1] || {};
    config.url = arguments[0];
  } else {
    config = config || {};
  }

  config = mergeConfig(this.defaults, config);

  if (config.method) {
    config.method = config.method.toLowerCase();
  } else if (this.defaults.method) {
    config.method = this.defaults.method.toLowerCase();
  } else {
    config.method = 'get';
  }

  return new Promise(function (resolve, reject) {
    try {
      dispatchRequest(config).then(resolve, reject)
    } catch (e) {
      reject(e);
    }
  }) 
};

從上é¢çš„設計å¯ä»¥å¾—知,在 request() 中會先å°å‚³å…¥çš„ config 與é è¨­çš„ config this.default åˆä½µï¼Œè£œé½Šä¸€äº›å¿…è¦çš„å±¬æ€§ï¼Œåƒæ˜¯ method。如果åˆä½µå¾Œ config 裡é¢é‚„是沒有 method 這個屬性,é è¨­æœƒä½¿ç”¨ GET 方法。最後觸發 dispatchRequest() 發出請求。

補充:dispatchRequest() 中會é‡å°è«‹æ±‚è³‡æ–™åšæœ€å¾Œçš„轉æ›ï¼ˆTransform request data)並ä¾ç…§ä¾ç…§åŸ·è¡Œç’°å¢ƒé¸æ“‡é©ç•¶çš„請求é©é…器發出請求。收到回應資料後,會å†å°‡å›žæ‡‰è³‡æ–™å°ˆæ›ï¼ˆTransform response data)éŽå¾Œå†äº¤ç”±å›žæ‡‰æ””截器處ç†ã€‚

補充: 由一開始的æ¢ä»¶åˆ¤æ–·å¯ä»¥å¾—知,其實 request() 方法æä¾›äº†å…©ç¨®ä½¿ç”¨æ–¹å¼ï¼Œå¦‚下:

// 官方並沒有é‡å° Axios 類別æä¾› interface

interface Axios {
  // ç•¥
  request<T = any, R = AxiosResponse<T>> (config: AxiosRequestConfig): Promise<R>;
  request<T = any, R = AxiosResponse<T>> (url: string, config: AxiosRequestConfig): Promise<R>;
  // ç•¥
}

ä¸éŽç¬¬äºŒç¨®æ–¹å¼åœ¨ AxiosInstance 並沒有æä¾›åž‹åˆ¥ï¼Œåœ¨æ–‡ä»¶ä¸­ä¹Ÿæ²’有æåŠï¼Œæˆ‘çŒœé€™æ‡‰è©²æ˜¯å› ç‚ºç¬¬äºŒç¨®æ–¹æ³•å…¶å¯¦åªæ˜¯ç‚ºäº†è¦æœå‹™ axios(url[, config]) 這種用法而產生的。

到這邊基本的 request() 已經å¯ä»¥é‹ä½œï¼Œä½†é‚„有一個很é‡è¦çš„功能沒有實è¸ï¼šæ””截器(Interceports)

加入攔截器(Interceports)

如果有使用éŽåƒæ˜¯ Express.js çš„æ§æ²¹ï¼Œå¯èƒ½æœƒè½éŽ ä¸­é–“ä»¶ï¼ˆMiddleware) 這個功能。攔截器的概念跟中間件很相似,å¯ä»¥ç”¨æ–¼ä¾‹å¦‚:處ç†èº«åˆ†é©—證,或是共åŒçš„é‚輯處ç†ã€‚

從上é¢çš„建構å¼ä¸­å¾—知,攔截器是é€éŽ InterceptorManager 類別建構出的實例。我們還沒有介紹到這個類別,但ç¾åœ¨å¯ä»¥å…ˆæƒ³åƒä»–是個陣列,並有一個 forEach 的方法å¯ä»¥é歷所有存在陣列中的攔截器。

Axios 實例上的攔截器åˆåˆ†æˆã€Œè«‹æ±‚攔截器ã€èˆ‡ã€Œå›žæ‡‰æ””截器ã€å…©å€‹ï¼Œåˆ†åˆ¥åœ¨ç™¼å‡ºè«‹æ±‚å‰åŸ·è¡Œï¼Œèˆ‡æŽ¥æ”¶åˆ°å›žæ‡‰å¾ŒåŸ·è¡Œã€‚å› æ­¤ axios.request() 的設計會這樣調整:

Axios.prototype.request = function request(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;
};

我們先將原本è¦è§¸ç™¼çš„ dispatchRequest 與一個 undefined 存進一個å為 chain 的陣列中。為什麼è¦é€™æ¨£å‘¢ï¼Ÿå¯ä»¥å…ˆçœ‹çœ‹ MDN 上 Promise.prototype.then() 的語法:

// å–自 MDN

p.then(onFulfilled[, onRejected]);

所以 chain 的設計會是 onFulfilled 與 onRejected 一組一組的陣列。在陣列中的 0ã€2ã€4ã€6 ä½ç½®æœƒæ˜¯å‰ä¸€å€‹éžåŒæ­¥æˆåŠŸå¾Œçš„ onFulfilled,1ã€3ã€5ã€7 ä½ç½®å‰‡æœƒæ˜¯ç•¶å‰ä¸€å€‹éžåŒæ­¥ç™¼ç”ŸéŒ¯èª¤æ™‚呼å«çš„ onRejected。

åœ¨æ¯æ¬¡ç™¼å‡ºè«‹æ±‚å‰ï¼Œéƒ½æœƒåˆ†åˆ¥å°‡æ‰€æœ‰æ””截器串在 chain çš„å‰å¾Œã€‚如果是「請求攔截器ã€ï¼Œå°±ç”¨ Array.prototype.unshift 一組一組放到 chain å‰é¢ï¼›å¦‚果是「回應攔截器ã€ï¼Œå°±ç”¨ Array.prototype.push 一組一組推到 chain 後é¢ã€‚

var chain = [
  /**
   * 請求攔截器
   */
  requestFulfilled, requestRejected, 
  /**
   * 發出請求
   */
  dispatchRequest, undefined, 
  /**
   * 回應攔截器
   */
  responseFulfilled, responseFulfilled
]

將所有攔截器串進 chain 後å†ç”¨ while 迴圈æ­é… Array.prototype.shift 將他們兩個一組的串起來:

Promise.resolve(config)
  /**
   * 請求攔截器
   * 發出請求å‰ä¸€å€‹ä¸€å€‹åŸ·è¡Œ
   */
  .then(requestFulfilled, requestRejected)
  /**
   * 發出請求
   */
  .then(dispatchRequest, undefined)
  /**
   * 回應攔截器
   * 接收到回應後一個一個執行 
   */
  .then(responseFulfilled, responseFulfilled)

這樣就完æˆäº†æ•´å€‹ axios.request() 的設計。

注æ„

const promise1 = promise.then(onFulfilled, onRejected)

const promise2 = promise.then(onFulfilled).catch(onRejected)

我們有兩種方法é‡å° Promise éŠçš„éŒ¯èª¤è™•ç†æœ‰å…©ç¨®å¯«æ³•,但者兩者的æ„義有很大的ä¸åŒã€‚在 promise1 與 promise2 中的 promise 發生錯誤,兩種的 onRejected 都å¯ä»¥æŽ¥åˆ°éŒ¯èª¤ï¼Œä½†å¦‚果錯誤是發生在 onFulfilled,在 promise1 çš„ onRejected 䏿œƒæŽ¥åˆ°éŒ¯èª¤ï¼Œå¯æ˜¯ promise2 å¯ä»¥æŽ¥åˆ°ã€‚

其他與 HTTP request methods å°å¯«åŒå方法

axios 除了 axios.request() å¯ä»¥ç™¼å‡ºè«‹æ±‚å¤–ï¼Œé‚„æœ‰åƒæ˜¯ get()ã€post()ã€put()ã€patch()ã€delete()ã€head()ã€options() 等方法。ä¸éŽæœ¬è³ªä¸Šä»–å€‘éƒ½æ˜¯å° axios.request() å†åšä¸€å±¤åŒ…è£ï¼Œæ‰€ä»¥ç›´æŽ¥ä¸Šç¨‹å¼ç¢¼ï¼š

/**
 * å°æ‡‰
 * - axios.delete(url[, config])
 * - axios.get(url[, config]) 
 * - axios.head(url[, config]) 
 * - axios.options(url[, config])  
 */
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  Axios.prototype[method] = function(url, config) {
    return this.request(mergeConfig(config || {}, {
      method: method,
      url: url,
      data: (config || {}).data
    }));
  };
});

/**
 * å°æ‡‰
 * - axios.post(url[, data[, config]])
 * - axios.put(url[, data[, config]])
 * - axios.patch(url[, data[, config]])
 */
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  Axios.prototype[method] = function(url, data, config) {
    return this.request(mergeConfig(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});

除了 getUri()外,以上就是 Axios 類別的實作設計。

攔截器類別(InterceptorManager Class)設計

先來確èªä¸€ä¸‹æ””截器的用法:

攔截器型別

export interface AxiosInterceptorManager<V> {
  use(onFulfilled?: (value: V) => V | Promise<V>, onRejected?: (error: any) => any): number;
  eject(id: number): void;
}

axios 文件 - 攔截器 連çµ

註冊攔截器 use():

// Add a request interceptor
axios.interceptors.request.use(function (config) {
    // Do something before request is sent
    return config;
  }, function (error) {
    // Do something with request error
    return Promise.reject(error);
  });

// Add a response interceptor
axios.interceptors.response.use(function (response) {
    // Any status code that lie within the range of 2xx cause this function to trigger
    // Do something with response data
    return response;
  }, function (error) {
    // Any status codes that falls outside the range of 2xx cause this function to trigger
    // Do something with response error
    return Promise.reject(error);
  });

如果è¦åˆªé™¤æŒ‡å®šæ””截器å¯ä»¥ç”¨ eject() 方法:

const myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);

知é“了攔截器的介é¢ï¼Œæˆ‘們å¯ä»¥é–‹å§‹çœ‹ InterceptorManager 是如何實作。

在 InterceptorManager 實例上,我們需è¦ä¸€å€‹é™£åˆ—存放已註冊的攔截器:

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

之後ä¸è«–是é€éŽ use() 註冊的攔截器或是用 eject() ç§»é™¤ï¼Œéƒ½æ˜¯å° handlers 這個陣列的æ“作,下é¢ä¸€ä¸€ä»‹ç´¹ã€‚

use() 註冊攔截器

分æž

註冊攔截器時,我們需è¦å‚³å…¥ fulfilled 與 rejected,並存放到 handlers 中。æ¯ç•¶è¨»å†Šä¸€å€‹æ””截器,就回傳一個數字用åšä¹‹å¾Œç§»é™¤ç”¨çš„åƒæ•¸ã€‚

如果記得 Axios.prototype.request 如何使用攔截器,我們就å¯ä»¥çŸ¥é“ handlers 的型別大致如下:

interface InterceptorHandler = {
  fulfilled?: (value: V) => V | Promise<V>,
  rejected?: (error: any) => any
}

type InterceptorHandlers = InterceptorHandler[]

實作

InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};

æ¯ç•¶è¨»å†Šä¸€å€‹æ””截器,就會將他包æˆç‰©ä»¶æŽ¨å…¥ handlers 陣列中,並回傳物件在該陣列的ä½ç½®ç‚ºä½•。之後如果è¦ç§»é™¤æ”¹æ””截器,åªè¦æ‰¾åˆ°é€™å€‹ä½ç½®ï¼Œå°±å¯ä»¥å°‡è©²æ””截器刪除。

eject() 刪除攔截器

分æž

éœ€è¦æŽ¥æ”¶ä¸€å€‹æŒ‡å®šä½ç½®ï¼ˆæ•¸å­—)將該ä½ç½®çš„æ””截器移除。

實作

InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null;
  }
};

é€™é‚Šéœ€è¦æ³¨æ„,刪除攔截器ä¸èƒ½å‹•到陣列的長度,在這裡是將原本的攔截器設定為 null,因為如果改變了長度,會造æˆå…ˆå‰è¨»å†Šæ””截器的ä½ç½®éŒ¯äº‚,之後刪除傳入其他的ä½ç½®å¾ˆæœ‰å¯èƒ½æœƒç„¡æ³•刪到真正想刪除的攔截器。

forEach() éæ­·ï¼ˆç§æœ‰æ–¹æ³•)

這裡會特別æåˆ°é€™å€‹ç§æœ‰æ–¹æ³•是因為我們å¯ä»¥ç™¼ç¾ï¼Œeject() 會將刪除的攔截器設定為 null。但å‰é¢çŸ¥é“ Axios.prototype.request 會將 handlers 中æ¯ä¸€å€‹æ””截器的 fulfilled 與 rejected æŽ¨å…¥é™£åˆ—ï¼Œä½†å¦‚æžœæ”¹æˆ null ä¸å°±æœƒå‡ºéŒ¯äº†å—Žï¼Ÿ

æ‰€ä»¥é€™é‚Šåªæ˜¯è¦æé†’,在這裡的 forEach éœ€è¦æª¢æŸ¥é歷到的ä½ç½®æ””截器是å¦é‚„存在,存在æ‰åŽ»å°‡æ””æˆªå™¨ä¸²èµ·ä¾†ï¼Œå¦å‰‡å°±ç•¥éŽè©²ä½ç½®ã€‚

InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
};

çµèªž

本篇中我們瞭解了é è¨­å°Žå…¥çš„ axios 與 axios.create() 建立的物件是如何é€éŽ createInstance åˆå§‹åŒ–,也瞭解到為什麼 axios å¯ä»¥ç•¶ä½œä¸€å€‹ function 使用,åˆå¯ä»¥åƒç‰©ä»¶ä¸€æ¨£å­˜å–到其他的方法與屬性。

後é¢ä¹Ÿçœ‹äº† Axios é¡žåˆ¥èˆ‡ä»–ç”¨ä¾†ç®¡ç†æ””截器的 InterceptorManager 類別的設計。在æ“作上活用了 Promise 與陣列的å„é …æ“作,真的éžå¸¸ä»¤äººçŽ©å‘³ã€‚

ç›®å‰é è¨ˆä¹‹å¾Œæœƒåˆ†åˆ¥æŽ¢è¨Ž axios 使用到的 XMLHttpRequest 介紹與 CancelToken 類別設計,å¯ä»¥æœŸå¾…一下。

åƒè€ƒè³‡æ–™

è«‹æˆ‘å–æ¯å’–å•¡

如果這裡的內容有幫助到你的話,一æ¯å’–å•¡å°±æ˜¯å°æˆ‘最大的鼓勵。

è«‹æˆ‘å–æ¯å’–å•¡
ÿÿÿÿ