概述
前端開發(fā)中,經(jīng)常會(huì)遇到發(fā)送異步請求的場景。一個(gè)功能齊全的 HTTP 請求庫可以大大降低我們的開發(fā)成本,提高開發(fā)效率。
axios 就是這樣一個(gè) HTTP 請求庫,近年來非常熱門。目前,它在 GitHub 上擁有超過 40,000 的 Star,許多權(quán)威人士都推薦使用它。
因此,我們有必要了解下 axios 是如何設(shè)計(jì),以及如何實(shí)現(xiàn) HTTP 請求庫封裝的。撰寫本文時(shí),axios 當(dāng)前版本為 0.18.0,我們以該版本為例,來閱讀和分析部分核心源代碼。axios 的所有源文件都位于 lib 文件夾中,下文中提到的路徑都是相對于 lib 來說的。
本文我們主要討論:
怎樣使用 axios。
axios 的核心模塊(請求、攔截器、撤銷)是如何設(shè)計(jì)和實(shí)現(xiàn)的?
axios 的設(shè)計(jì)優(yōu)點(diǎn)是什么?
如何使用 axios
要理解 axios 的設(shè)計(jì),首先需要看一下如何使用 axios。我們舉一個(gè)簡單的例子來說明下 axios API 的使用。
發(fā)送請求
method:'get',
url:'http://bit.ly/2mTM3nY',
responseType:'stream'
})
.then(function(response) {
response.data.pipe(fs.createWriteStream('ada_lovelace.jpg'))
});
這是一個(gè)官方示例。從上面的代碼中可以看到,axios 的用法與 jQuery 的 ajax 方法非常類似,兩者都返回一個(gè) Promise 對象(在這里也可以使用成功回調(diào)函數(shù),但還是更推薦使用 Promise 或 await),然后再進(jìn)行后續(xù)操作。
這個(gè)實(shí)例很簡單,不需要我解釋了。我們再來看看如何添加一個(gè)攔截器函數(shù)。
添加攔截器函數(shù)
return config;
}, function (error) {
return Promise.reject(error);
});
axios.interceptors.response.use(function (response) {
return response;
}, function (error) {
return Promise.reject(error);
});
從上面的代碼,我們可以知道:發(fā)送請求之前,我們可以對請求的配置參數(shù)(config)做處理;在請求得到響應(yīng)之后,我們可以對返回?cái)?shù)據(jù)做處理。當(dāng)請求或響應(yīng)失敗時(shí),我們還能指定對應(yīng)的錯(cuò)誤處理函數(shù)。
撤銷 HTTP 請求
在開發(fā)與搜索相關(guān)的模塊時(shí),我們經(jīng)常要頻繁地發(fā)送數(shù)據(jù)查詢請求。一般來說,當(dāng)我們發(fā)送下一個(gè)請求時(shí),需要撤銷上個(gè)請求。因此,能撤銷相關(guā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('請求撤銷了', thrown.message);
} else {
}
});
axios.post('/user/12345', {
name: '新名字'
}, {
cancelToken: source.token
}).
source.cancel('用戶撤銷了請求');
從上例中可以看到,在 axios 中,使用基于 CancelToken 的撤銷請求方案。然而,該提案現(xiàn)已撤回,詳情如 點(diǎn)這里。具體的撤銷請求的實(shí)現(xiàn)方法,將在后面的源代碼分析的中解釋。
axios 核心模塊的設(shè)計(jì)和實(shí)現(xiàn)
通過上面的例子,我相信每個(gè)人都對 axios 的使用有一個(gè)大致的了解了。下面,我們將根據(jù)模塊分析 axios 的設(shè)計(jì)和實(shí)現(xiàn)。下面的圖片,是我在本文中會(huì)介紹到的源代碼文件。如果您感興趣,最好在閱讀時(shí)克隆相關(guān)的代碼,這能加深你對相關(guān)模塊的理解。

HTTP 請求模塊
請求模塊的代碼放在了 core/dispatchRequest.js 文件中,這里我只展示了一些關(guān)鍵代碼來簡單說明:
module.exports = function dispatchRequest(config) {
throwIfCancellationRequested(config);
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ī)范的適配器函數(shù)來替代原來的模塊(一般來說,我們不會(huì)這樣做,但它是一個(gè)松散耦合的擴(kuò)展點(diǎn))。
在 defaults.js 文件中,我們可以看到相關(guān)適配器的選擇邏輯——根據(jù)當(dāng)前容器的一些獨(dú)特屬性和構(gòu)造函數(shù),來確定使用哪個(gè)適配器。
function getDefaultAdapter() {
var adapter;
if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
adapter = require('./adapters/http');
} else if (typeof XMLHttpRequest !== 'undefined') {
adapter = require('./adapters/xhr');
}
return adapter;
}
axios 中的 XHR 模塊相對簡單,它是對 XMLHTTPRequest 對象的封裝,這里我就不再解釋了。有興趣的同學(xué),可以自己閱讀源源碼看看,源碼位于 adapters/xhr.js 文件中。
攔截器模塊
現(xiàn)在讓我們看看 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)代碼相當(dāng)長,這里我會(huì)簡單地討論相關(guān)設(shè)計(jì)思想:
1.chain 是一個(gè)執(zhí)行隊(duì)列。隊(duì)列的初始值是一個(gè)攜帶配置(config)參數(shù)的 Promise 對象。
2.在執(zhí)行隊(duì)列中,初始函數(shù) dispatchRequest 用來發(fā)送請求,為了與 dispatchRequest對應(yīng),我們添加了一個(gè) undefined。添加 undefined 的原因是需要給 Promise 提供成功和失敗的回調(diào)函數(shù),從下面代碼里的 promise = promise.then(chain.shift(), chain.shift()); 我們就能看出來。因此,函數(shù) dispatchRequest 和 undefiend 可以看成是一對函數(shù)。
3.在執(zhí)行隊(duì)列 chain 中,發(fā)送請求的 dispatchReqeust 函數(shù)處于中間位置。它前面是請求攔截器,使用 unshift 方法插入;它后面是響應(yīng)攔截器,使用 push 方法插入,在 dispatchRequest 之后。需要注意的是,這些函數(shù)都是成對的,也就是一次會(huì)插入兩個(gè)。
瀏覽上面的 request 函數(shù)代碼,我們大致知道了怎樣使用攔截器。下一步,來看看怎樣撤銷一個(gè) HTTP 請求。
撤銷請求模塊
與撤銷請求相關(guān)的模塊位于 Cancel/ 文件夾下,現(xiàn)在我們來看下相關(guān)核心代碼。
首先,我們來看下基礎(chǔ) Cancel 類。它是一個(gè)用來記錄撤銷狀態(tài)的類,具體代碼如下:
function Cancel(message) {
this.message = message;
}
Cancel.prototype.toString = function toString() {
return 'Cancel' + (this.message ? ': ' + this.message : '');
};
Cancel.prototype.__CANCEL__ = true;
使用 CancelToken 類時(shí),需要向它傳遞一個(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) {
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
};
};
adapters/xhr.js 文件中,撤銷請求的地方是這樣寫的:
if (config.cancelToken) {
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
request.abort();
reject(cancel);
request = null;
});
}
通過上面的撤銷 HTTP請求的例子,讓我們簡要地討論一下相關(guān)的實(shí)現(xiàn)邏輯:
1.在需要撤銷的請求中,調(diào)用 CancelToken 類的 source 方法類進(jìn)行初始化,會(huì)得到一個(gè)包含 CancelToken 類實(shí)例 A 和 cancel 方法的對象。
2.當(dāng) source 方法正在返回實(shí)例 A 的時(shí)候,一個(gè)處于 pending 狀態(tài)的 promise 對象初始化完成。在將實(shí)例 A 傳遞給 axios 之后,promise 就可以作為撤銷請求的觸發(fā)器使用了。
3.當(dāng)調(diào)用通過 source 方法返回的 cancel 方法后,實(shí)例 A 中 promise 狀態(tài)從 pending 變成 fulfilled,然后立即觸發(fā) then 回調(diào)函數(shù)。于是 axios 的撤銷方法——request.abort() 被觸發(fā)了。
axios 這樣設(shè)計(jì)的好處是什么?
發(fā)送請求函數(shù)的處理邏輯
如前幾章所述,axios 不將用來發(fā)送請求的 dispatchRequest 函數(shù)看做一個(gè)特殊函數(shù)。實(shí)際上,dispatchRequest 會(huì)被放在隊(duì)列的中間位置,以便保證隊(duì)列處理的一致性和代碼的可讀性。
適配器的處理邏輯
在適配器的處理邏輯上,http 和 xhr 模塊(一個(gè)是在 Node.js 中用來發(fā)送請求的,一個(gè)是在瀏覽器里用來發(fā)送請求的)并沒有在 dispatchRequest 函數(shù)中使用,而是各自作為單獨(dú)的模塊,默認(rèn)通過 defaults.js 文件中的配置方法引入的。因此,它不僅確保了兩個(gè)模塊之間的低耦合,而且還為將來的用戶提供了定制請求發(fā)送模塊的空間。
撤銷 HTTP 請求的邏輯
在撤銷 HTTP 請求的邏輯中,axios 設(shè)計(jì)使用 Promise 來作為觸發(fā)器,將 resolve 函數(shù)暴露在外面,并在回調(diào)函數(shù)里使用。它不僅確保了內(nèi)部邏輯的一致性,而且還確保了在需要撤銷請求時(shí),不需要直接更改相關(guān)類的樣例數(shù)據(jù),以避免在很大程度上入侵其他模塊。
以上是小編借鑒自己看的文章編輯的 希望能給大家?guī)韼椭。。。。?/p>
喜歡的點(diǎn)歌攢?。。。。。。?/p>