自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何實現(xiàn)一個HTTP請求庫?——axios源碼閱讀與分析

開發(fā) 前端
在前端開發(fā)過程中,我們經(jīng)常會遇到需要發(fā)送異步請求的情況。而使用一個功能齊全,接口完善的HTTP請求庫,能夠在很大程度上減少我們的開發(fā)成本,提高我們的開發(fā)效率。axios是一個在近些年來非?;鸬囊粋€HTTP請求庫,目前在GitHub中已經(jīng)擁有了超過40K的star,受到了各位大佬的推薦。

[[238375]]

概述

在前端開發(fā)過程中,我們經(jīng)常會遇到需要發(fā)送異步請求的情況。而使用一個功能齊全,接口完善的HTTP請求庫,能夠在很大程度上減少我們的開發(fā)成本,提高我們的開發(fā)效率。

axios是一個在近些年來非?;鸬囊粋€HTTP請求庫,目前在GitHub中已經(jīng)擁有了超過40K的star,受到了各位大佬的推薦。

今天,我們就來看下,axios到底是如何設(shè)計的,其中又有哪些值得我們學(xué)習(xí)的地方。我在寫這邊文章時,axios的版本為0.18.0。我們就以這個版本的代碼為例,來進(jìn)行具體的源碼閱讀和分析。當(dāng)前axios所有源碼文件都在lib文件夾中,因此我們下文中提到的路徑均是指lib文件夾中的路徑。

本文的主要內(nèi)容有:

  • 如何使用axios
  • axios的核心模塊是如何設(shè)計與實現(xiàn)的(請求、攔截器、撤回)
  • axios的設(shè)計有什么值得借鑒的地方

如何使用axios

想要了解axios的設(shè)計,我們首先需要來看下axios是如何使用的。我們通過一個簡單示例來介紹以下axios的API。

發(fā)送請求

 

  1. axios({  
  2.   method:'get' 
  3.   url:'http://bit.ly/2mTM3nY' 
  4.   responseType:'stream'  
  5. })  
  6.   .then(function(response) {  
  7.   response.data.pipe(fs.createWriteStream('ada_lovelace.jpg'))  
  8. }); 

這是一個官方的API示例。從上面的代碼中我們可以看到,axios的用法與jQuery的ajax很相似,都是通過返回一個Promise(也可以通過success的callback,不過建議使用Promise或者await)來繼續(xù)后面的操作。

這個代碼示例很簡單,我就不過多贅述了,下面讓我們來看下如何添加一個過濾器函數(shù)。

增加攔截器(Interceptors)函數(shù)

 

  1. // 增加一個請求攔截器,注意是2個函數(shù),一個處理成功,一個處理失敗,后面會說明這種情況的原因  
  2. axios.interceptors.request.use(function (config) { 
  3.     // 請求發(fā)送前處理  
  4.     return config;  
  5.   }, function (error) {  
  6.     // 請求錯誤后處理  
  7.     return Promise.reject(error);  
  8.   });  
  9.  
  10. // 增加一個響應(yīng)攔截器  
  11. axios.interceptors.response.use(function (response) {  
  12.     // 針對響應(yīng)數(shù)據(jù)進(jìn)行處理  
  13.     return response;  
  14.   }, function (error) {  
  15.     // 響應(yīng)錯誤后處理  
  16.     return Promise.reject(error);  
  17.   }); 

通過上面的示例我們可以知道:在請求發(fā)送前,我們可以針對請求的config參數(shù)進(jìn)行數(shù)據(jù)處理;而在請求響應(yīng)后,我們也能針對返回的數(shù)據(jù)進(jìn)行特定的操作。同時,在請求失敗和響應(yīng)失敗時,我們都可以進(jìn)行特定的錯誤處理。

取消HTTP請求

在完成搜索相關(guān)的功能時,我們經(jīng)常會需要頻繁的發(fā)送請求來進(jìn)行數(shù)據(jù)查詢的情況。通常來說,我們在下一次請求發(fā)送時,就需要取消上一次請求。因此,取消請求相關(guān)的功能也是一個優(yōu)點。axios取消請求的示例代碼如下:

 

  1. const CancelToken = axios.CancelToken;  
  2. const source = CancelToken.source();  
  3. axios.get('/user/12345', {  
  4.   cancelToken: source.token  
  5. }).catch(function(thrown) {  
  6.   if (axios.isCancel(thrown)) {  
  7.     console.log('Request canceled', thrown.message);  
  8.   } else {  
  9.     // handle error  
  10.   }  
  11. });  
  12.  
  13. axios.post('/user/12345', {  
  14.   name'new name'  
  15. }, {  
  16.   cancelToken: source.token  
  17. })  
  18.  
  19. // cancel the request (the message parameter is optional)  
  20. source.cancel('Operation canceled by the user.'); 

通過上面的示例我們可以看到,axios使用的是基于CancelToken的一個撤回提案。不過,目前該提案已經(jīng)被撤回,具體詳情可以見此處。具體的撤回實現(xiàn)方法我們會在后面的章節(jié)源碼分析的時候進(jìn)行說明。

axios的核心模塊是如何設(shè)計與實現(xiàn)的

通過上面的例子,我相信大家對axios的使用方法都有了一個大致的了解。下面,我們將按照模塊來對axios的設(shè)計與實現(xiàn)進(jìn)行分析。下圖是我們在這篇博客中將會涉及到的相關(guān)的axios的文件,如果讀者有興趣的話,可以通過clone相關(guān)代碼結(jié)合博客進(jìn)行閱讀,這樣能夠加深對相關(guān)模塊的理解。

HTTP請求模塊

作為核心模塊,axios發(fā)送請求相關(guān)的代碼位于core/dispatchReqeust.js文件中。由于篇幅有限,下面我選取部分重點的源碼進(jìn)行簡單的介紹:

 

  1. module.exports = function dispatchRequest(config) {  
  2.     throwIfCancellationRequested(config);  
  3.  
  4.     // 其他源碼   
  5.     // default adapter是一個可以判斷當(dāng)前環(huán)境來選擇使用Node還是XHR進(jìn)行請求發(fā)送的模塊  
  6.     var adapter = config.adapter || defaults.adapter;  
  7.  
  8.     return adapter(config).then(function onAdapterResolution(response) {  
  9.         throwIfCancellationRequested(config);  
  10.  
  11.         // 其他源碼  
  12.  
  13.         return response;  
  14.     }, function onAdapterRejection(reason) {  
  15.         if (!isCancel(reason)) {  
  16.             throwIfCancellationRequested(config);  
  17.  
  18.             // 其他源碼  
  19.             return Promise.reject(reason);  
  20.         });  
  21. }; 

通過上面的代碼和示例我們可以知道,dispatchRequest方法是通過獲取config.adapter來得到發(fā)送請求的模塊的,我們自己也可以通過傳入符合規(guī)范的adapter函數(shù)來替換掉原生的模塊(雖然一般不會這么做,不過也算是一個松耦合擴(kuò)展點)。

在default.js文件中,我們能夠看到相關(guān)的adapter選擇邏輯,即根據(jù)當(dāng)前容器中特有的一些屬性和構(gòu)造函數(shù)來進(jìn)行判斷。

 

  1. function getDefaultAdapter() {  
  2.     var adapter;  
  3.     // 只有Node.js才有變量類型為process的類  
  4.     if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {  
  5.         // Node.js請求模塊  
  6.         adapter = require('./adapters/http');  
  7.     } else if (typeof XMLHttpRequest !== 'undefined') {  
  8.         // 瀏覽器請求模塊  
  9.         adapter = require('./adapters/xhr');  
  10.     }  
  11.     return adapter;  

axios中XHR模塊較為簡單,為XMLHTTPRequest對象的封裝,我們在這里就不過多進(jìn)行介紹了,有興趣的同學(xué)可以自行閱讀,代碼位于adapters/xhr.js文件中。

攔截器模塊

了解了dispatchRequest實現(xiàn)的HTTP請求發(fā)送模塊,我們來看下axios是如何處理請求和響應(yīng)攔截函數(shù)的。讓我們看下axios中請求的統(tǒng)一入口request函數(shù)。

 

  1. Axios.prototype.request = function request(config) {  
  2.     // 其他代碼  
  3.     var chain = [dispatchRequest, undefined]; 
  4.     var promise = Promise.resolve(config);  
  5.     this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {  
  6.         chain.unshift(interceptor.fulfilled, interceptor.rejected);  
  7.     });  
  8.  
  9.     this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {  
  10.         chain.push(interceptor.fulfilled, interceptor.rejected);  
  11.     });  
  12.  
  13.     while (chain.length) {  
  14.         promise = promise.then(chain.shift(), chain.shift());  
  15.     }  
  16.     return promise;  
  17. }; 

這個函數(shù)是axios發(fā)送請求的入口,因為函數(shù)實現(xiàn)比較長,我就簡單說一下相關(guān)的設(shè)計思路:

  1. chain是一個執(zhí)行隊列。這個隊列的初始值,是一個帶有config參數(shù)的Promise。
  2. 在chain執(zhí)行隊列中,插入了初始的發(fā)送請求的函數(shù)dispatchReqeust和與之對應(yīng)的undefined。后面需要增加一個undefined是因為在Promise中,需要一個success和一個fail的回調(diào)函數(shù),這個從代碼promise = promise.then(chain.shift(), chain.shift());就能夠看出來。因此,dispatchReqeust和undefined我們可以成為一對函數(shù)。
  3. 在chain執(zhí)行隊列中,發(fā)送請求的函數(shù)dispatchReqeust是處于中間的位置。它的前面是請求攔截器,通過unshift方法放入;它的后面是響應(yīng)攔截器,通過push放入。要注意的是,這些函數(shù)都是成對的放入,也就是一次放入兩個。

通過上面的request代碼,我們大致知道了攔截器的使用方法。接下來,我們來看下如何取消一個HTTP請求。

取消請求模塊

取消請求相關(guān)的模塊在Cancel/文件夾中。讓我們來看下相關(guān)的重點代碼。

首先,讓我們來看下元數(shù)據(jù)Cancel類。它是用來記錄取消狀態(tài)一個類,具體代碼如下:   

  1. function Cancel(message) {  
  2.      this.message = message;  
  3.    }   
  4.    Cancel.prototype.toString = function toString() {  
  5.      return 'Cancel' + (this.message ? ': ' + this.message : '');  
  6.    };  
  7.    Cancel.prototype.__CANCEL__ = true

而在CancelToken類中,它通過傳遞一個Promise的方法來實現(xiàn)了HTTP請求取消,然我們看下具體的代碼:

 

  1. function CancelToken(executor) {  
  2.     if (typeof executor !== 'function') {  
  3.         throw new TypeError('executor must be a function.');  
  4.     } 
  5.     var resolvePromise;  
  6.     this.promise = new Promise(function promiseExecutor(resolve) {  
  7.         resolvePromise = resolve;  
  8.     }); 
  9.  
  10.     var token = this;  
  11.     executor(function cancel(message) {  
  12.         if (token.reason) {  
  13.             // Cancellation has already been requested  
  14.             return 
  15.         }  
  16.         token.reason = new Cancel(message);  
  17.         resolvePromise(token.reason);  
  18.     });  
  19.  
  20.  
  21. CancelToken.source = function source() {  
  22.     var cancel;  
  23.     var token = new CancelToken(function executor(c) {  
  24.         cancel = c;  
  25.     });  
  26.     return {  
  27.         token: token,  
  28.         cancel: cancel  
  29.     };  
  30. }; 

而在adapter/xhr.js文件中,有與之相對應(yīng)的取消請求的代碼:

 

  1. if (config.cancelToken) {  
  2.     // 等待取消  
  3.     config.cancelToken.promise.then(function onCanceled(cancel) {  
  4.         if (!request) {  
  5.             return 
  6.         } 
  7.         request.abort();  
  8.         reject(cancel);  
  9.         // 重置請求  
  10.         request = null 
  11.     });  

結(jié)合上面的取消HTTP請求的示例和這些代碼,我們來簡單說下相關(guān)的實現(xiàn)邏輯:

  1. 在可能需要取消的請求中,我們初始化時調(diào)用了source方法,這個方法返回了一個CancelToken類的實例A和一個函數(shù)cancel。
  2. 在source方法返回實例A中,初始化了一個在pending狀態(tài)的promise。我們將整個實例A傳遞給axios后,這個promise被用于做取消請求的觸發(fā)器。
  3. 當(dāng)source方法返回的cancel方法被調(diào)用時,實例A中的promise狀態(tài)由pending變成了fulfilled,立刻觸發(fā)了then的回調(diào)函數(shù),從而觸發(fā)了axios的取消邏輯——request.abort()。

axios的設(shè)計有什么值得借鑒的地方

發(fā)送請求函數(shù)的處理邏輯

在之前的章節(jié)中有提到過,axios在處理發(fā)送請求的dispatchRequest函數(shù)時,沒有當(dāng)做一個特殊的函數(shù)來對待,而是采用一視同仁的方法,將其放在隊列的中間位置,從而保證了隊列處理的一致性,提高了代碼的可閱讀性。

Adapter的處理邏輯

在adapter的處理邏輯中,axios沒有把http和xhr兩個模塊(一個用于Node.js發(fā)送請求,另一個則用于瀏覽器端發(fā)送請求)當(dāng)成自身的模塊直接在dispatchRequest中直接飲用,而是通過配置的方法在default.js文件中進(jìn)行默認(rèn)引入。這樣既保證了兩個模塊間的低耦合性,同時又能夠為今后用戶需要自定義請求發(fā)送模塊保留了余地。

取消HTTP請求的處理邏輯

在取消HTTP請求的邏輯中,axios巧妙的使用了一個Promise來作為觸發(fā)器,將resolve函數(shù)通過callback中參數(shù)的形式傳遞到了外部。這樣既能夠保證內(nèi)部邏輯的連貫性,也能夠保證在需要進(jìn)行取消請求時,不需要直接進(jìn)行相關(guān)類的示例數(shù)據(jù)改動,***程度上避免了侵入其他的模塊。

總結(jié)

本文對axios相關(guān)的使用方式、設(shè)計思路和實現(xiàn)方法進(jìn)行了詳細(xì)的介紹。讀者能夠通過上述文章,了解axios的設(shè)計思想,同時能夠在axios的代碼中,學(xué)習(xí)到關(guān)于模塊封裝和交互等相關(guān)的經(jīng)驗。

 

由于篇幅原因,本文僅針對axios的核心模塊進(jìn)行了分解和介紹,如果對其他代碼有興趣的同學(xué),可以去GitHub進(jìn)行查看。 

責(zé)任編輯:龐桂玉 來源: segmentfault
相關(guān)推薦

2022-04-08 08:26:03

JavaHTTP請求

2021-07-27 14:50:15

axiosHTTP前端

2020-10-20 14:01:16

HTTP

2021-11-19 07:54:59

Axios網(wǎng)絡(luò)源碼

2021-04-22 05:37:14

Axios 開源項目HTTP 攔截器

2021-09-09 10:23:08

GinNetHttp

2021-10-29 12:01:11

HTTP代碼前端

2021-01-28 07:21:13

算法虛擬DOM前端

2021-11-22 16:12:34

Axios Axios-Retry前端

2024-09-18 08:10:06

2011-12-26 16:39:43

局部函數(shù)

2024-10-05 00:00:06

HTTP請求處理容器

2024-01-08 13:47:00

代碼分析工具

2021-07-20 10:30:46

Golanghttp語言

2010-06-29 13:18:31

HTTP協(xié)議

2019-04-24 15:06:37

Http服務(wù)器協(xié)議

2019-12-20 09:31:23

TCPHTTP瀏覽器

2021-07-04 10:07:04

Virtual DO閱讀源碼虛擬DOM

2022-03-24 14:49:57

HTTP前端

2024-06-05 08:42:24

點贊
收藏

51CTO技術(shù)棧公眾號