成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

剛出鍋的 Axios 網絡請求源碼閱讀筆記,你會嗎?

開發 開發工具
項目中一直都有用到 Axios 作為網絡請求工具,用它更要懂它,因此為了更好地發揮 Axios 在項目的價值,以及日后能夠得心應手地使用它,筆者決定從源碼層面好好欣賞一下它的美貌!

[[435862]]

項目中一直都有用到 Axios 作為網絡請求工具,用它更要懂它,因此為了更好地發揮 Axios 在項目的價值,以及日后能夠得心應手地使用它,筆者決定從源碼層面好好欣賞一下它的美貌!

Axios是一款基于 Promise 并可用于瀏覽器和 Node.js 的網絡請求庫。

  • Github:https://github.com/axios/axios
  • NPM:https://www.npmjs.com/package/axios
  • Docs:https://axios-http.com/docs/intro

最近,Axios 官方文檔終于變好看了,支持多語言切換,閱讀更清晰,使用起來也更加舒適!作為一款受全球歡迎的網絡請求庫,有必要偷學一下其中的架構設計、編碼方式。

本篇文章從源碼層面主要分析 Axios 的功能實現、設計模式、以及分享 Axios 中一些筆者認為比較“精彩”的地方!

本文主要內容結構如下,大家按需食用:

一、Axios 項目概況

本次分析的 Axios 版本是:v0.24.0

通過簡單的瀏覽 package.json、文件及目錄,可以得知 axios 工程采用了如下三方依賴:

名稱 說明
Grunt[1] JavaScript 任務運行器
dtslint[2] TypeScript 類型聲明&樣式校驗工具
TypeScript[3] 支持TS環境下開發
Webpack[4] JavaScript 模塊打包工具
karma[5] 測試用例檢查器
mocha[6] 多功能的 JavaScript 測試框架
sinojs[7] 提供spies, stub, mock,推薦文章《Sinon 入門,看這篇文章就夠了[8]
follow-redirects[9] http(s)重定向,NodeJS模塊

這里省略了對一些工具介紹,但可以發現,Axios 開發項目的主功能依賴并不多,換句話說是只有 follow-redirects作為了“使用依賴”,其他都是編譯、測試、框架層面的東西,可以看出官方團隊在對于 Axios 有多么注質量和穩定性,畢竟是全球都在用的工具。

Axios 中相關代碼都在 lib/ 目錄下(建議逐行閱讀):

  1. ├── adapters  // 網絡請求,NodeJS 環境使用 NodeJS 的 http 模塊,瀏覽器使用 XHR 
  2. │   ├── README.md 
  3. │   ├── http.js  // Node.js 環境使用 
  4. │   └── xhr.js  // 瀏覽器環境使用 
  5. ├── helpers  // 一些功能輔助工具函數,看文件名可基本知道干啥的 
  6. │   ├── README.md 
  7. │   ├── bind.js 
  8. │   ├── buildURL.js 
  9. │   ├── combineURLs.js 
  10. │   ├── cookies.js 
  11. │   ├── deprecatedMethod.js 
  12. │   ├── isAbsoluteURL.js 
  13. │   ├── isAxiosError.js 
  14. │   ├── isURLSameOrigin.js 
  15. │   ├── normalizeHeaderName.js 
  16. │   ├── parseHeaders.js 
  17. │   ├── spread.js 
  18. │   └── validator.js 
  19. ├── cancel  // 取消網絡請求的處理 
  20. │   ├── Cancel.js  // 取消請求 
  21. │   ├── CancelToken.js  // 取消 Token 
  22. │   └── isCancel.js  // 判斷是否取消請求的函數方法 
  23. ├── core  // 核心功能 
  24. │   ├── Axios.js  // Axios 對象 
  25. │   ├── InterceptorManager.js  // 攔截器管理 
  26. │   ├── README.md 
  27. │   ├── buildFullPath.js  // 構造完成的請求 URL 
  28. │   ├── createError.js  // 創建錯誤,拋出異常 
  29. │   ├── dispatchRequest.js  // 請求分發,用于區分調用 http 還是 xhr 
  30. │   ├── enhanceError.js  // 增強錯誤??????????????? 
  31. │   ├── mergeConfig.js  // 合并配置參數 
  32. │   ├── settle.js  // 根據請求響應狀態,改變 Promise 狀態 
  33. │   └── transformData.js  // 數據格式轉換 
  34. ├── env  // 無關緊要,沒啥用,與發包版本有關 
  35. │   ├── README.md 
  36. │   └── data.js 
  37. ├── defaults.js  // 默認參數/初始化參數配置 
  38. ├── utils.js  // 提供簡單的通用的工具函數 
  39. └── axios.js  // 入口文件,初始化并導出 axios 對象 

有了一個簡單的代碼功能組織架構熟悉后,對于串聯 Axios 的功能很有好處,另外,從上述文件和文件夾的命名,很容易讓人意識到這是一個什么功能的文件。

“高內聚、低耦合”的真言,在 Axios 中應該算是一個運用得很好的例子。

二、Axios 網絡請求流程圖

梳理了一張 Axios 發起請求、響應請求的執行流程圖,希望可以給大家一個完整流程的概念,便于理解后續的源碼分析。

Axios 網絡請求流程圖

三、Axios API 設計

我們在使用 Axios 的時候,會覺得 Axios 的使用特別方便,其原因就是 Axios 中針對同一功能實現了不同的 API,便于大家在各種場景下的變通擴展使用。

例如,發起一個 GET 請求的寫法有:

  1. // 第一種 
  2. axios('https://xxx.com/api/userInfo?uid=1'
  3.  
  4. // 第二種 
  5. axios.get('https://xxx.com/api/userInfo?uid=1'
  6.  
  7. // 第三種 
  8. axios({ 
  9.   method: 'GET'
  10.   url: 'https://xxx.com/api/userInfo?uid=1' 
  11. }) 

Axios 請求的核心方法僅兩種:

  1. axios(config) 
  2.  
  3. // or 
  4.  
  5. axios(url[, config]) 

我們知道一個網絡請求的方式會有 GET、POST、PUT、DELETE 等,為了使用更加語義化,Axios 對外暴露了別名 API:

  1. axios.request(config) 
  2. axios.get(url[, config]) 
  3. axios.delete(url[, config]) 
  4. axios.head(url[, config]) 
  5. axios.options(url[, config]) 
  6.  
  7. axios.post(url[, data[, config]]) 
  8. axios.put(url[, data[, config]]) 
  9. axios.patch(url[, data[, config]]) 

通過遍歷擴展axios對象原型鏈上的方法:

  1. // Provide aliases for supported request methods 
  2. utils.forEach(['delete''get''head''options'], function forEachMethodNoData(method) { 
  3.   /*eslint func-names:0*/ 
  4.   Axios.prototype[method] = function(url, config) { 
  5.     return this.request(mergeConfig(config || {}, { 
  6.       method: method, 
  7.       url: url, 
  8.       data: (config || {}).data 
  9.     })); 
  10.   }; 
  11. }); 
  12.  
  13. utils.forEach(['post''put''patch'], function forEachMethodWithData(method) { 
  14.   /*eslint func-names:0*/ 
  15.   Axios.prototype[method] = function(url, data, config) { 
  16.     return this.request(mergeConfig(config || {}, { 
  17.       method: method, 
  18.       url: url, 
  19.       data: data 
  20.     })); 
  21.   }; 
  22. }); 

能夠如上的直接循環列表賦值,得益于 Axios 將核心的請求功能單獨放到了 Axios.prototype.request 方法中,該方法的 TS 定義為:

  1. Axios.request(config: any, ...args: any[]): any 

在其方法(Axios.request())內會對外部傳參數類型做判斷,并選擇組裝正確的請求參數:

  1. // 生成規范的 config,抹平 API(函數入參)差異 
  2. if (typeof config === 'string') { 
  3.   // 處理了第一個參數是 url 字符串的情況 request(url[, config]) 
  4.   config = arguments[1] || {}; 
  5.   config.url = arguments[0]; 
  6. else { 
  7.   config = config || {}; 
  8.  
  9. // 合并默認配置 
  10. config = mergeConfig(this.defaults, config); 
  11.  
  12. // 將請求方法轉小寫字母,默認為 get 方法 
  13. if (config.method) { 
  14.   config.method = config.method.toLowerCase(); 
  15. else if (this.defaults.method) { 
  16.   config.method = this.defaults.method.toLowerCase(); 
  17. else { 
  18.   config.method = 'get'

以此來抹平了各種類型請求以及所需傳入參數之間的差異性!

四、Axios 工廠模式創建實例

默認 Axios 導出了一個單例,導出了一個實例化后的單例,所以我們可以直接引入后就可以調用 Axios 的方法。

在某些場景下,我們的項目中可能對接了多個業務方,那么請求中的 base URL 就不一樣,因此有沒有辦法創建多個 Axios 實例?

那就是使用 axios.create([config]) 方法創建多個實例。

考慮到多實例這樣的實際需求,Axios 對外暴露了 create() 方法,在 Axios 內部中,往導出的 axios 實例上綁定了用于創建本身實例的工廠方法:

  1. /** 
  2.  * Create an instance of Axios 
  3.  * 
  4.  * @param {Object} defaultConfig The default config for the instance 
  5.  * @return {Axios} A new instance of Axios 
  6.  */ 
  7. function createInstance(defaultConfig) { 
  8.   var context = new Axios(defaultConfig); 
  9.   var instance = bind(Axios.prototype.request, context); 
  10.  
  11.   // Copy axios.prototype to instance 
  12.   utils.extend(instance, Axios.prototype, context); 
  13.  
  14.   // Copy context to instance 
  15.   utils.extend(instance, context); 
  16.  
  17.   // Factory for creating new instances 
  18.   instance.create = function create(instanceConfig) { 
  19.     return createInstance(mergeConfig(defaultConfig, instanceConfig)); 
  20.   }; 
  21.  
  22.   return instance; 

這里的實現值得一說的地方在于:

  1. instance.create = function create(instanceConfig) { 
  2.  
  3. return createInstance(mergeConfig(defaultConfig, instanceConfig)); 
  4.  
  5. }; 

在創建 axios 實例的工廠方法內,綁定工廠方法到實例的 create 屬性上。為什么不是在工廠方法外綁定吶?這是我們可能的習慣做法,Axios 之前確實也是這么做的。

為什么挪到了內部?可以看看這條 PR: Allow axios.create(options) to be used recursively[10]

原因簡單來說就是,用戶自己創建的實例依然可以調用 create 方法創建新的實例,例如:

  1. const axios = require('axios'); 
  2.  
  3. const jsonClient = axios.create({ 
  4.   responseType: 'json' // 該項配置可以在后續創建的實例中復用,而不必重復編碼 
  5. }); 
  6.  
  7. const serviceOne = jsonClient.create({ 
  8.   baseURL: 'https://service.one/' 
  9. }); 
  10.  
  11. const serviceTwo = jsonClient.create({ 
  12.   baseURL: 'https://service.two/' 
  13. }); 

這樣有助于復用實例的公共參數復用,減少重復編碼。

五、網絡請求適配器

在文件 ./defaults.js 中生成了默認完整的 Request Config 參數。

其中 config.adapter 字段表明當前應該使用 ./adapters/目錄下的 http.js 還是 xhr.js 模塊。

  1. // 根據當前使用環境,選擇使用的網絡請求適配器 
  2. function getDefaultAdapter() { 
  3.   var adapter; 
  4.   if (typeof XMLHttpRequest !== 'undefined') { 
  5.     // For browsers use XHR adapter 
  6.     adapter = require('./adapters/xhr'); 
  7.   } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') { 
  8.     // For node use HTTP adapter 
  9.     adapter = require('./adapters/http'); 
  10.   } 
  11.   return adapter; 

這里使用了設計模式中的適配器模式,通過判斷不同環境下是否支持方法的方式,選擇正確的網絡請求模塊,便可以實現官網所說的支持 NodeJS 和瀏覽器環境。

六、轉換請求體和響應體數據

這是 Axios 貼在官網的核心功能之一,且提到了可以自動轉換響應體內容為 JSON 數據。

默認請求配置中初始化的請求/響應轉換器數組。

自動嘗試轉換響應數據為 JSON 格式

transformRequest 和 transformResponse 字段是一個數組類型,因此我們還可以向其中增加自定義的轉換器。

一般來講我們只會通過復寫 transitional 字段來控制響應數據的轉換與否,但可以作為擴展 Axios 的一個點,留了口子,這一點考慮得也很到位。

七、請求攔截器&響應攔截器

可以通過攔截器來提前處理請求前和收到響應前的一些處理方法。

7.1 攔截器的使用

攔截器用于在 .then() 和 .catch() 前注入并執行的一些方法。

  1. // 通過 use 方法,添加一個請求攔截器 
  2. axios.interceptors.request.use(function (config) { 
  3.     // 在發送請求前干點啥,.then() 處理之前,比如修改 request config 
  4.     return config; 
  5.   }, function (error) { 
  6.     // 在發起請求發生錯誤后,.catch() 處理之前干點啥 
  7.     return Promise.reject(error); 
  8.   }); 
  9.  
  10. // 通過 use 方法,添加一個響應攔截器 
  11. axios.interceptors.response.use(function (response) { 
  12.     // 只要響應網絡狀態碼是 2xx 的都會觸發 
  13.     // 干點啥 
  14.     return response; 
  15.   }, function (error) { 
  16.     // 狀態碼不是 2xx 的會觸發 
  17.     // 發生錯誤了,干點啥 
  18.     return Promise.reject(error); 
  19.   }); 

7.2 攔截管理器

Axios 將請求和響應的過程包裝成了 Promise,那么 Axios 是如何實現攔截器在 .then() 和 .catch() 執行前執行吶?

可以很容易猜到通過組裝一條 Promise 執行鏈即可!

來看看 Axios 在請求函數中如何實現:

首先是 Axios 對象中初始化了 攔截管理器:

  1. function Axios(instanceConfig) { 
  2.   this.defaults = instanceConfig; 
  3.   this.interceptors = { 
  4.     request: new InterceptorManager(), 
  5.     response: new InterceptorManager() 
  6.   }; 

來到 ./lib/core/InterceptorManager.js 文件下,對于攔截管理器:

  1. // 攔截管理器對象 
  2. function InterceptorManager() { 
  3.   this.handlers = []; 
  4.  
  5. /** 
  6.  * 添加新的管理器,定義了 use 方法 
  7.  * 
  8.  * @param {Function} fulfilled 處理 `Promise` 執行 `then` 的函數方法 
  9.  * @param {Function} rejected 處理 `Promise` 執行 `reject` 的函數方法 
  10.  * 
  11.  * @return {Number} 返回一個 ID 值用于移除攔截器 
  12.  */ 
  13. InterceptorManager.prototype.use = function use(fulfilled, rejected, options) { 
  14.   this.handlers.push({ 
  15.     fulfilled: fulfilled, 
  16.     rejected: rejected, 
  17.     // 默認不同步 
  18.     synchronous: options ? options.synchronous : false
  19.     // 定義是否執行當前攔截器的函數或布爾值 
  20.     runWhen: options ? options.runWhen : null  
  21.   }); 
  22.   return this.handlers.length - 1; // ID 值實際就是當前攔截器的數組索引 
  23. }; 
  24.  
  25. /** 
  26.  * 從棧中移除指定 id 的攔截器 
  27.  * 
  28.  * @param {Number} id use 方法返回的 id 值 
  29.  */ 
  30. InterceptorManager.prototype.eject = function eject(id) { 
  31.   if (this.handlers[id]) { 
  32.     this.handlers[id] = null; // 刪除攔截器,但索引會保留 
  33.   } 
  34. }; 
  35.  
  36. /** 
  37.  * 迭代所有注冊的攔截器 
  38.  * 該方法會跳過因攔截器被刪除而值為 null 的索引 
  39.  * 
  40.  * @param {Function} 調用每個有效攔截器的函數 
  41.  */ 
  42. InterceptorManager.prototype.forEach = function forEach(fn) { 
  43.   utils.forEach(this.handlers, function forEachHandler(h) { 
  44.     if (h !== null) { 
  45.       fn(h); 
  46.     } 
  47.   }); 
  48. }; 

迭代所有注冊的攔截器是一個 FIFS(first come first served,先到先服務)隊列執行順序的方法。

7.3 組裝攔截器與請求執行鏈

在 ./lib/core/Axios.js 文件中,Axios 對象定義了 request 方法,其中將網絡請求、請求攔截器和響應攔截器組裝。

默認返回一個還未執行網絡請求的 Promise 執行鏈,如果設置了同步,則會立即執行請求過程,并返回請求結果的 Promise 對象,也就是官方文檔中提到的 Axios 還支持 Promise API。

函數詳細的分析,都已經注釋在如下代碼中:

  1. /** 
  2.  * Dispatch a request 
  3.  * 
  4.  * @param {Object} config 傳入的用戶自定義配置,并和默認配置 merge 
  5.  */ 
  6. Axios.prototype.request = function request(config) { 
  7.   // 省略 ... 
  8.  
  9.   // 請求攔截器執行鏈 
  10.   var requestInterceptorChain = []; 
  11.   // 同步請求攔截器 
  12.   var synchronousRequestInterceptors = true
  13.   // 遍歷請求攔截器 
  14.   this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { 
  15.     // 判斷 runWhen 如果是函數,則執行函數,結果若為 false,則不執行當前攔截器 
  16.     if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) { 
  17.       return
  18.     } 
  19.     // 判斷當前攔截器是否同步 
  20.     synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous; 
  21.     // 插入 requestInterceptorChain 數組首位 
  22.     // 效果:[interceptor.fulfilled, interceptor.rejected, ...] 
  23.     requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected); 
  24.   }); 
  25.  
  26.   // 響應攔截器執行鏈 
  27.   var responseInterceptorChain = []; 
  28.   // 遍歷所有的響應攔截器 
  29.   this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { 
  30.     // 插入 responseInterceptorChain 尾部 
  31.     // 效果:[ ..., interceptor.fulfilled, interceptor.rejected] 
  32.     responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected); 
  33.   }); 
  34.  
  35.   var promise; 
  36.  
  37.   // 如果非同步 
  38.   // 一般大家在使用 axios.interceptors.request.use 都沒有傳遞第三個配置參數 
  39.   // 所以一般情況下會走這個邏輯 
  40.   if (!synchronousRequestInterceptors) { 
  41.     var chain = [dispatchRequest, undefined]; 
  42.     // 將請求攔截器執行鏈放到 chain 數組頭部 
  43.     Array.prototype.unshift.apply(chain, requestInterceptorChain); 
  44.     // 將響應攔截器執行鏈放到 chain 數組末尾 
  45.     chain = chain.concat(responseInterceptorChain); 
  46.     // 給 promise 賦值 Promise 對象,并注入 request config 
  47.     promise = Promise.resolve(config); 
  48.     // 循環 chain 數組,組合成 Promise 執行鏈 
  49.     while (chain.length) { 
  50.       // 正好 resolve 和 reject 對應方法,兩兩一組 
  51.       promise = promise.then(chain.shift(), chain.shift()); 
  52.     } 
  53.     // 返回 Promise 執行鏈 
  54.     return promise; 
  55.   } 
  56.  
  57.   // 同步方式 
  58.   var newConfig = config; 
  59.   // 循環并執行所有請求攔截器 
  60.   while (requestInterceptorChain.length) { 
  61.     var onFulfilled = requestInterceptorChain.shift(); 
  62.     var onRejected = requestInterceptorChain.shift(); 
  63.     try { 
  64.       // 執行定義請求前的“請求攔截器” then 處理方法 
  65.       newConfig = onFulfilled(newConfig); 
  66.     } catch (error) { 
  67.       // 執行定義請求前的“請求攔截器” catch 處理方法 
  68.       onRejected(error); 
  69.       break; 
  70.     } 
  71.   } 
  72.  
  73.   try { 
  74.     // 執行網絡請求 
  75.     promise = dispatchRequest(newConfig); 
  76.   } catch (error) { 
  77.     return Promise.reject(error); 
  78.   } 
  79.  
  80.   // 循環并執行所有響應攔截器 
  81.   while (responseInterceptorChain.length) { 
  82.     promise = promise.then(responseInterceptorChain.shift(), responseInterceptorChain.shift()); 
  83.   } 
  84.   // 返回 Promise 對象 
  85.   return promise; 
  86. }; 

可以看到由于請求攔截器和響應攔截器使用了 unshift 和 push,那么 use 攔截器的先后順序就有變動。

通過如上代碼的分析,可以得知若有多個攔截器的執行順序規則是:

  • 請求攔截器:先 use,后執行
  • 響應攔截器:先 use,先執行

關于攔截器執行這部分,涉及到一個 PR改動: Requests unexpectedly delayed due to an axios internal promise[11],推薦大家閱讀一下,有助于熟悉微任務和宏任務。

改動的原因:如果請求攔截器中存在一些長時間的任務,會使得使用 axios 的網絡請相較于不使用 axios 的網絡請求會延后,為此,通過為攔截管理器增加 synchronous 和 runWhen 字段,來實現同步執行請求方法。

八、取消網絡請求

在網絡請求中,會遇到許多非預期的請求取消,當然也有主動取消請求的時候,例如,用戶獲取 id=1 的新聞數據,需要耗時 30s,用戶等不及了,就返回查看 id=2 的新聞詳情,此時我們可以在代碼中主動取消 id=1 的網絡請求,節省網絡資源。

8.1 如何取消 Axios 請求

通過 CancleToken.source() 工廠方法創建取消請求的實例 source

在發起請求的 request Config 中設置 cancelToken 值為 source.token

在需要主動取消請求的地方調用:source.cancle()

  1. const CancelToken = axios.CancelToken; 
  2. const source = CancelToken.source(); 
  3.  
  4. axios.get('/user/12345', { 
  5.   cancelToken: source.token 
  6. }).catch(function (thrown) { 
  7.   if (axios.isCancel(thrown)) { 
  8.     console.log('Request canceled', thrown.message); 
  9.   } else { 
  10.     // handle error 
  11.   } 
  12. }); 
  13.  
  14. axios.post('/user/12345', { 
  15.   name'new name' 
  16. }, { 
  17.   cancelToken: source.token 
  18. }) 
  19.  
  20. // 主動取消請求 (提示信息是可選的參數) 
  21. source.cancel('Operation canceled by the user.'); 

同一個 source 實例調用取消 cancle() 方法時,會取消所有含有當前實例 source.token 的請求。

8.2 取消請求功能的原理

想必大家也很好奇是怎么實現取消網絡請求功能的,實際上有了上述的基礎,把 Axios 的請求想象成為一條事件執行鏈,執行鏈中任意一處發生了異常,都會中斷整個請求。

整個請求執行鏈中的設計了,首先來看:axios.CancelToken.source():

  1. /** 
  2.  * Returns an object that contains a new `CancelToken` and a function that, when called, 
  3.  * cancels the `CancelToken`. 
  4.  */ 
  5. CancelToken.source = function source() { 
  6.   var cancel; 
  7.   var token = new CancelToken(function executor(c) { 
  8.     cancel = c; 
  9.   }); 
  10.   return { 
  11.     token: token, 
  12.     cancel: cancel 
  13.   }; 
  14. }; 

該工廠方法返回了一個對象,該對象包含了一個 token(取消令牌,CancleToken 對象的實例),以及一個取消與 token 映射綁定的取消請求方法 cancle()。

其中 new CancelToken() 會創建 CancleToken 的單例,通過傳入函數方式,拿到了取消請求的回調函數,該函數內會構造 token 取消的原因,并通過執行 resolvePromise(),主動 reslove。

同樣是一個微任務,當主動調用 cancle() 方法后,會調用 resolvePromise(reason),此時就會給當前 cancleToken 實例的 reason 字段賦值“請求取消的原因”:

  1. function CancelToken(executor) { 
  2.   if (typeof executor !== 'function') { 
  3.     throw new TypeError('executor must be a function.'); 
  4.   } 
  5.  
  6.   // 初始化一個 promise 屬性,resolvePromise 變量指向 resolve 
  7.   var resolvePromise; 
  8.   this.promise = new Promise(function promiseExecutor(resolve) { 
  9.     resolvePromise = resolve; 
  10.   }); 
  11.  
  12.   // 賦值 token 為當前對象的實例 
  13.   var token = this; 
  14.  
  15.   // 省略... 
  16.  
  17.   // 執行外部傳入的初始化方法,將取消請求的方法,賦值給返回對象的 cancel 屬性 
  18.   executor(function cancel(message) { 
  19.     if (token.reason) { 
  20.       // Cancellation has already been requested 
  21.       return
  22.     } 
  23.  
  24.     token.reason = new Cancel(message); 
  25.     resolvePromise(token.reason); 
  26.   }); 

在 ./lib/core/dispatchRequest.js 文件中:

  1. function throwIfCancellationRequested(config) { 
  2.   // 當 request config 中有實例化 cancelToken 時 
  3.   // 執行 throwIfRequested() 方法 
  4.   // throwIfRequested() 方法在 cancleToken 實例的 reason 字段有值時 
  5.   // 拋出異常 
  6.   if (config.cancelToken) { 
  7.     config.cancelToken.throwIfRequested(); 
  8.   } 
  9.   // 判斷 config.signal.aborted 值為真的時候拋出異常 
  10.   // 該值時通過 new AbortController().signal,不過目前暫時未用到 
  11.   // 官方文檔上暫也暫未更新相關內容 
  12.   if (config.signal && config.signal.aborted) { 
  13.     throw new Cancel('canceled'); 
  14.   } 
  15.  
  16. module.exports = function dispatchRequest(config) { 
  17.   // 準備發起請求前檢查 
  18.   throwIfCancellationRequested(config); 
  19.    
  20.   // 省略... 
  21.    
  22.   var adapter = config.adapter || defaults.adapter; 
  23.   return adapter(config).then(function onAdapterResolution(response) { 
  24.     // 請求成功后檢查 
  25.     throwIfCancellationRequested(config); 
  26.     // 省略... 
  27.     return response; 
  28.   }, function onAdapterRejection(reason) { 
  29.     if (!isCancel(reason)) { 
  30.       // 請求發生錯誤時候檢查 
  31.       throwIfCancellationRequested(config); 
  32.       // 省略... 
  33.     } 
  34.     // 省略... 
  35.  
  36.     return Promise.reject(reason); 
  37.   }); 

在文章前邊分析攔截器的時候講到了 dispatchRequest() 在請求攔截器之后執行。

在請求前,請求成功、失敗后三個時機點,都會通過 throwIfCancellationRequested() 函數檢查是否取消了請求,throwIfCancellationRequested() 函數判斷了 cancleToken.reason 是否有值,如果有則拋出異常并中斷請求 Promise 執行鏈。

九、CSRF 防御

Axios 支持防御 CSRF(Cross-site request forgery,跨站請求偽造)攻擊,而防御 CSRF 攻擊的最簡單方式就是加 Token。

CSRF 的攻擊可以簡述為:服務器錯把攻擊者的請求當成了正常用戶的請求。

加一個 Token 為什么就能解決吶?首先 Token 是服務端隨用戶每次請求動態生成下發的,用戶在提交表單、查詢數據等行為的時候,需要在網絡請求體加上這個臨時性的 Token 值,攻擊者無法在三方網站中獲取當前 Token,因此服務端就可以通過驗證 Token 來區分是否是正常用戶的請求。

Axios 在請求配置中提供了兩個字段:

  1. // cookie 中攜帶的 Token 名稱,通過該名稱可以從 cookie 中拿到 Token 值 
  2. xsrfCookieName: 'XSRF-TOKEN'
  3. // 請求 Header 中攜帶的 Token 名稱,通過該成名可從 Header 中拿到 Token 值 
  4. xsrfHeaderName: 'X-XSRF-TOKEN'

用于附加驗證防御 CSRF 攻擊的 Token。

十、值得一說的自定義工具庫

在 Axios 內,沒有引入其他例如 lodash 的工具函數依賴,都在自己內部按需實現了工具函數,提供給整個項目使用。

個人非常喜歡這種做法,尤其是在一個 ES5 的工具庫下,這樣做不僅代碼易讀,與此同時還顯得非常得純粹、干凈、清晰!

如果團隊內有這種訴求,建議可以寫一個 ESM 模塊的工具庫,這樣做以后,在打包 Tree Shaking 時,打包的結果應該能更加干凈。

總結

總體來說,Axios 涉及到的設計模式就有:單例模式、工廠模式、職責鏈模式、適配器模式,因此絕對是值得學習的一個工具庫,梳理之后不僅利于我們靈活使用其 API,更有助于根據業務去自定義擴展封裝網絡請求,將網絡請求統一收口。 

與此同時,Axios 絕對是一個可以作為軟件工程編碼的學習范本,其中的文件夾結構,功能設計,功能解耦,按需封裝工具類,以及靈活運用設計模式都是值得揣度回味。

責任編輯:武曉燕 來源: DYBOY
相關推薦

2021-11-22 16:12:34

Axios Axios-Retry前端

2013-12-24 10:05:04

memcached

2018-07-30 16:31:00

javascriptaxioshttp

2018-12-29 14:14:32

2019-05-07 15:49:27

AI人工智能藝術

2010-07-13 10:40:30

唐駿

2021-08-19 15:36:09

數據備份存儲備份策略

2024-03-29 12:50:00

項目分層模型

2021-02-15 14:48:31

Hive語法sql

2022-03-15 08:36:46

遞歸查詢SQL

2024-02-22 08:31:26

數據恢復工具MySQL回滾SQL

2014-07-03 15:40:09

Apache Spar

2021-04-14 06:53:52

C# 修飾符 Public

2021-04-16 15:02:11

CAP理論分布式

2012-06-20 10:47:25

Team Leader

2012-06-20 15:01:25

iOS開發

2023-02-27 10:45:16

2018-03-18 08:32:30

網絡安全區塊鏈隱私

2023-12-04 07:09:53

函數遞歸python
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 中文字字幕一区二区三区四区五区 | 蜜桃视频在线观看免费视频网站www | 97伦理影院 | 精品国产青草久久久久96 | 中国一级特黄毛片大片 | 国产超碰人人爽人人做人人爱 | 99一级毛片 | 成人欧美一区二区三区1314 | 国产精品一区二区不卡 | 四虎永久免费黄色影片 | 狠狠入ady亚洲精品经典电影 | 欧美日韩久久精品 | 国产视频精品在线 | 成人免费淫片aa视频免费 | 性视频一区 | 国内自拍偷拍一区 | 黄在线免费观看 | 亚洲男人的天堂网站 | 日韩av高清 | 精品久久久久久久久久久久久久 | 成人亚洲精品久久久久软件 | 欧美精品在线免费观看 | 欧美2区 | 午夜男人天堂 | 中文字幕第一页在线 | 欧美理论片在线观看 | 精品婷婷| 在线观看亚洲精品视频 | xxx.在线观看 | 97国产精品视频人人做人人爱 | 国产成人在线视频播放 | 亚洲国产黄 | 久久久久综合 | 毛片a级毛片免费播放100 | 亚洲午夜一区二区 | 亚洲高清在线 | 久久国产一区二区三区 | 国产精品一区在线观看 | 国产福利一区二区 | 午夜视频一区二区三区 | 午夜视频在线播放 |