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

axios是一個(gè)在近些年來非常火的一個(gè)HTTP請(qǐng)求庫(kù),目前在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)的(請(qǐng)求、攔截器、撤回)
- axios的設(shè)計(jì)有什么值得借鑒的地方
如何使用axios
想要了解axios的設(shè)計(jì),我們首先需要來看下axios是如何使用的。我們通過一個(gè)簡(jiǎn)單示例來介紹以下axios的API。
發(fā)送請(qǐng)求
- 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è)代碼示例很簡(jiǎn)單,我就不過多贅述了,下面讓我們來看下如何添加一個(gè)過濾器函數(shù)。
增加攔截器(Interceptors)函數(shù)
- // 增加一個(gè)請(qǐng)求攔截器,注意是2個(gè)函數(shù),一個(gè)處理成功,一個(gè)處理失敗,后面會(huì)說明這種情況的原因
- axios.interceptors.request.use(function (config) {
- // 請(qǐng)求發(fā)送前處理
- return config;
- }, function (error) {
- // 請(qǐng)求錯(cuò)誤后處理
- return Promise.reject(error);
- });
- // 增加一個(gè)響應(yīng)攔截器
- axios.interceptors.response.use(function (response) {
- // 針對(duì)響應(yīng)數(shù)據(jù)進(jìn)行處理
- return response;
- }, function (error) {
- // 響應(yīng)錯(cuò)誤后處理
- return Promise.reject(error);
- });
通過上面的示例我們可以知道:在請(qǐng)求發(fā)送前,我們可以針對(duì)請(qǐng)求的config參數(shù)進(jìn)行數(shù)據(jù)處理;而在請(qǐng)求響應(yīng)后,我們也能針對(duì)返回的數(shù)據(jù)進(jìn)行特定的操作。同時(shí),在請(qǐng)求失敗和響應(yīng)失敗時(shí),我們都可以進(jìn)行特定的錯(cuò)誤處理。
取消HTTP請(qǐng)求
在完成搜索相關(guān)的功能時(shí),我們經(jīng)常會(huì)需要頻繁的發(fā)送請(qǐng)求來進(jìn)行數(shù)據(jù)查詢的情況。通常來說,我們?cè)谙乱淮握?qǐng)求發(fā)送時(shí),就需要取消上一次請(qǐng)求。因此,取消請(qǐng)求相關(guān)的功能也是一個(gè)優(yōu)點(diǎn)。axios取消請(qǐng)求的示例代碼如下:
- 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)的
通過上面的例子,我相信大家對(duì)axios的使用方法都有了一個(gè)大致的了解。下面,我們將按照模塊來對(duì)axios的設(shè)計(jì)與實(shí)現(xiàn)進(jìn)行分析。下圖是我們?cè)谶@篇博客中將會(huì)涉及到的相關(guān)的axios的文件,如果讀者有興趣的話,可以通過clone相關(guān)代碼結(jié)合博客進(jìn)行閱讀,這樣能夠加深對(duì)相關(guān)模塊的理解。
HTTP請(qǐng)求模塊
作為核心模塊,axios發(fā)送請(qǐng)求相關(guān)的代碼位于core/dispatchReqeust.js文件中。由于篇幅有限,下面我選取部分重點(diǎn)的源碼進(jìn)行簡(jiǎn)單的介紹:
- module.exports = function dispatchRequest(config) {
- throwIfCancellationRequested(config);
- // 其他源碼
- // default adapter是一個(gè)可以判斷當(dāng)前環(huán)境來選擇使用Node還是XHR進(jìn)行請(qǐng)求發(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ā)送請(qǐng)求的模塊的,我們自己也可以通過傳入符合規(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請(qǐng)求模塊
- adapter = require('./adapters/http');
- } else if (typeof XMLHttpRequest !== 'undefined') {
- // 瀏覽器請(qǐng)求模塊
- adapter = require('./adapters/xhr');
- }
- return adapter;
- }
axios中XHR模塊較為簡(jiǎn)單,為XMLHTTPRequest對(duì)象的封裝,我們?cè)谶@里就不過多進(jìn)行介紹了,有興趣的同學(xué)可以自行閱讀,代碼位于adapters/xhr.js文件中。
攔截器模塊
了解了dispatchRequest實(shí)現(xiàn)的HTTP請(qǐng)求發(fā)送模塊,我們來看下axios是如何處理請(qǐng)求和響應(yīng)攔截函數(shù)的。讓我們看下axios中請(qǐng)求的統(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ā)送請(qǐng)求的入口,因?yàn)楹瘮?shù)實(shí)現(xiàn)比較長(zhǎng),我就簡(jiǎn)單說一下相關(guān)的設(shè)計(jì)思路:
- chain是一個(gè)執(zhí)行隊(duì)列。這個(gè)隊(duì)列的初始值,是一個(gè)帶有config參數(shù)的Promise。
- 在chain執(zhí)行隊(duì)列中,插入了初始的發(fā)送請(qǐng)求的函數(shù)dispatchReqeust和與之對(duì)應(yīng)的undefined。后面需要增加一個(gè)undefined是因?yàn)樵赑romise中,需要一個(gè)success和一個(gè)fail的回調(diào)函數(shù),這個(gè)從代碼promise = promise.then(chain.shift(), chain.shift());就能夠看出來。因此,dispatchReqeust和undefined我們可以成為一對(duì)函數(shù)。
- 在chain執(zhí)行隊(duì)列中,發(fā)送請(qǐng)求的函數(shù)dispatchReqeust是處于中間的位置。它的前面是請(qǐng)求攔截器,通過unshift方法放入;它的后面是響應(yīng)攔截器,通過push放入。要注意的是,這些函數(shù)都是成對(duì)的放入,也就是一次放入兩個(gè)。
通過上面的request代碼,我們大致知道了攔截器的使用方法。接下來,我們來看下如何取消一個(gè)HTTP請(qǐng)求。
取消請(qǐng)求模塊
取消請(qǐng)求相關(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請(qǐng)求取消,然我們看下具體的代碼:
- 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文件中,有與之相對(duì)應(yīng)的取消請(qǐng)求的代碼:
- if (config.cancelToken) {
- // 等待取消
- config.cancelToken.promise.then(function onCanceled(cancel) {
- if (!request) {
- return;
- }
- request.abort();
- reject(cancel);
- // 重置請(qǐng)求
- request = null;
- });
- }
結(jié)合上面的取消HTTP請(qǐng)求的示例和這些代碼,我們來簡(jiǎn)單說下相關(guān)的實(shí)現(xiàn)邏輯:
- 在可能需要取消的請(qǐng)求中,我們初始化時(shí)調(diào)用了source方法,這個(gè)方法返回了一個(gè)CancelToken類的實(shí)例A和一個(gè)函數(shù)cancel。
- 在source方法返回實(shí)例A中,初始化了一個(gè)在pending狀態(tài)的promise。我們將整個(gè)實(shí)例A傳遞給axios后,這個(gè)promise被用于做取消請(qǐng)求的觸發(fā)器。
- 當(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ā)送請(qǐng)求函數(shù)的處理邏輯
在之前的章節(jié)中有提到過,axios在處理發(fā)送請(qǐng)求的dispatchRequest函數(shù)時(shí),沒有當(dāng)做一個(gè)特殊的函數(shù)來對(duì)待,而是采用一視同仁的方法,將其放在隊(duì)列的中間位置,從而保證了隊(duì)列處理的一致性,提高了代碼的可閱讀性。
Adapter的處理邏輯
在adapter的處理邏輯中,axios沒有把http和xhr兩個(gè)模塊(一個(gè)用于Node.js發(fā)送請(qǐng)求,另一個(gè)則用于瀏覽器端發(fā)送請(qǐng)求)當(dāng)成自身的模塊直接在dispatchRequest中直接飲用,而是通過配置的方法在default.js文件中進(jìn)行默認(rèn)引入。這樣既保證了兩個(gè)模塊間的低耦合性,同時(shí)又能夠?yàn)榻窈笥脩粜枰远x請(qǐng)求發(fā)送模塊保留了余地。
取消HTTP請(qǐng)求的處理邏輯
在取消HTTP請(qǐng)求的邏輯中,axios巧妙的使用了一個(gè)Promise來作為觸發(fā)器,將resolve函數(shù)通過callback中參數(shù)的形式傳遞到了外部。這樣既能夠保證內(nèi)部邏輯的連貫性,也能夠保證在需要進(jìn)行取消請(qǐng)求時(shí),不需要直接進(jìn)行相關(guān)類的示例數(shù)據(jù)改動(dòng),***程度上避免了侵入其他的模塊。
總結(jié)
本文對(duì)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)。
由于篇幅原因,本文僅針對(duì)axios的核心模塊進(jìn)行了分解和介紹,如果對(duì)其他代碼有興趣的同學(xué),可以去GitHub進(jìn)行查看。
本文題目:如何實(shí)現(xiàn)一個(gè)HTTP請(qǐng)求庫(kù)?——axios源碼閱讀與分析
文章來源:http://www.5511xx.com/article/cceigdg.html


咨詢
建站咨詢
