<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 7036字,需瀏覽 15分鐘

           ·

          2021-01-13 19:05

          作者:hjava

          原文:https://segmentfault.com/a/1190000015747143

          概述

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

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

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

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

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

          如何使用axios

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

          發(fā)送請求

          axios({
          ??method:'get',
          ??url:'http://bit.ly/2mTM3nY',
          ??responseType:'stream'
          })
          ??.then(function(response)?{
          ??response.data.pipe(fs.createWriteStream('ada_lovelace.jpg'))
          });

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

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

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

          //?增加一個(gè)請求攔截器,注意是2個(gè)函數(shù),一個(gè)處理成功,一個(gè)處理失敗,后面會(huì)說明這種情況的原因
          axios.interceptors.request.use(function?(config)?{
          ????//?請求發(fā)送前處理
          ????return?config;
          ??},?function?(error)?{
          ????//?請求錯(cuò)誤后處理
          ????return?Promise.reject(error);
          ??});

          //?增加一個(gè)響應(yīng)攔截器
          axios.interceptors.response.use(function?(response)?{
          ????//?針對響應(yīng)數(shù)據(jù)進(jìn)行處理
          ????return?response;
          ??},?function?(error)?{
          ????//?響應(yīng)錯(cuò)誤后處理
          ????return?Promise.reject(error);
          ??});

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

          取消HTTP請求

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

          const?CancelToken?=?axios.CancelToken;
          const?source?=?CancelToken.source();

          axios.get('/user/12345',?{
          ??cancelToken:?source.token
          }).catch(function(thrown)?{
          ??if?(axios.isCancel(thrown))?{
          ????console.log('Request?canceled',?thrown.message);
          ??}?else?{
          ????//?handle?error
          ??}
          });

          axios.post('/user/12345',?{
          ??name:?'new?name'
          },?{
          ??cancelToken:?source.token
          })

          //?cancel?the?request?(the?message?parameter?is?optional)
          source.cancel('Operation?canceled?by?the?user.');

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

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

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

          img

          HTTP請求模塊

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

          module.exports?=?function?dispatchRequest(config)?{
          ????throwIfCancellationRequested(config);

          ????//?其他源碼

          ????//?default?adapter是一個(gè)可以判斷當(dāng)前環(huán)境來選擇使用Node還是XHR進(jìn)行請求發(fā)送的模塊
          ????var?adapter?=?config.adapter?||?defaults.adapter;?

          ????return?adapter(config).then(function?onAdapterResolution(response)?{
          ????????throwIfCancellationRequested(config);

          ????????//?其他源碼

          ????????return?response;
          ????},?function?onAdapterRejection(reason)?{
          ????????if?(!isCancel(reason))?{
          ????????????throwIfCancellationRequested(config);

          ????????????//?其他源碼

          ????????????return?Promise.reject(reason);
          ????????});
          };

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

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

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

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

          攔截器模塊

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

          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;
          };

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

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

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

          取消請求模塊

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

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

          ????function?Cancel(message)?{
          ??????this.message?=?message;
          ????}

          ????Cancel.prototype.toString?=?function?toString()?{
          ??????return?'Cancel'?+?(this.message???':?'?+?this.message?:?'');
          ????};

          ????Cancel.prototype.__CANCEL__?=?true;

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

          function?CancelToken(executor)?{
          ????if?(typeof?executor?!==?'function')?{
          ????????throw?new?TypeError('executor?must?be?a?function.');
          ????}

          ????var?resolvePromise;
          ????this.promise?=?new?Promise(function?promiseExecutor(resolve)?{
          ????????resolvePromise?=?resolve;
          ????});

          ????var?token?=?this;
          ????executor(function?cancel(message)?{
          ????????if?(token.reason)?{
          ????????????//?Cancellation?has?already?been?requested
          ????????????return;
          ????????}

          ????????token.reason?=?new?Cancel(message);
          ????????resolvePromise(token.reason);
          ????});
          }

          CancelToken.source?=?function?source()?{
          ????var?cancel;
          ????var?token?=?new?CancelToken(function?executor(c)?{
          ????????cancel?=?c;
          ????});
          ????return?{
          ????????token:?token,
          ????????cancel:?cancel
          ????};
          };

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

          if?(config.cancelToken)?{
          ????//?等待取消
          ????config.cancelToken.promise.then(function?onCanceled(cancel)?{
          ????????if?(!request)?{
          ????????????return;
          ????????}

          ????????request.abort();
          ????????reject(cancel);
          ????????//?重置請求
          ????????request?=?null;
          ????});
          }

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

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

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

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

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

          Adapter的處理邏輯

          在adapter的處理邏輯中,axios沒有把http和xhr兩個(gè)模塊(一個(gè)用于Node.js發(fā)送請求,另一個(gè)則用于瀏覽器端發(fā)送請求)當(dāng)成自身的模塊直接在dispatchRequest中直接飲用,而是通過配置的方法在default.js文件中進(jìn)行默認(rèn)引入。這樣既保證了兩個(gè)模塊間的低耦合性,同時(shí)又能夠?yàn)榻窈笥脩粜枰远x請求發(fā)送模塊保留了余地。

          取消HTTP請求的處理邏輯

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

          總結(jié)

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

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

          如果有任何疑問或者觀點(diǎn),歡迎隨時(shí)留言討論。


          瀏覽 43
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本一级片在线看 | 色老板成人永久免费视频 | 操自拍| 亚洲人妻系列 | 激情黄页 |