這篇文章分享給大家的內容是關於axios原始碼解析如何實作一個HTTP請求庫,內容很詳細,接下來我們就來看看具體的內容,希望可以幫助到大家。
在前端開發過程中,我們常常會遇到需要傳送非同步請求的情況。而使用一個功能齊全,介面完善的HTTP請求庫,能夠大幅減少我們的開發成本,提高我們的開發效率。
axios是近年來非常火熱的一個HTTP請求庫,目前在GitHub中已經擁有了超過40K的star,受到了各位大佬的推薦。
今天,我們就來看下,axios到底是如何設計的,其中又有哪些值得我們學習的地方。我在寫這邊文章時,axios的版本為0.18.0。我們就以這個版本的程式碼為例,來進行具體的原始碼閱讀與分析。目前axios所有原始碼檔案都在lib
資料夾中,因此我們下文中提到的路徑都是指lib
資料夾中的路徑。
本文的主要內容有:
如何使用axios
axios的核心模組是如何設計與實作的(請求、攔截器、撤回)
axios的設計有什麼值得借鏡的地方
axios({ method:'get', url:'http://bit.ly/2mTM3nY', responseType:'stream' }) .then(function(response) { response.data.pipe(fs.createWriteStream('ada_lovelace.jpg')) });
// 增加一个请求拦截器,注意是2个函数,一个处理成功,一个处理失败,后面会说明这种情况的原因 axios.interceptors.request.use(function (config) { // 请求发送前处理 return config; }, function (error) { // 请求错误后处理 return Promise.reject(error); }); // 增加一个响应拦截器 axios.interceptors.response.use(function (response) { // 针对响应数据进行处理 return response; }, function (error) { // 响应错误后处理 return Promise.reject(error); });
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的一個撤回提案。不過,目前該提案已被撤回,具體詳情可見此處。具體的撤回實作方法我們會在後面的章節源碼分析的時候進行說明。
core/dispatchReqeust.js檔案中。由於篇幅有限,下面我選取部分重點的原始碼進行簡單的介紹:
module.exports = function dispatchRequest(config) { throwIfCancellationRequested(config); // 其他源码 // default adapter是一个可以判断当前环境来选择使用Node还是XHR进行请求发送的模块 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 來得到發送請求的模組的,我們自己也可以透過傳入符合規範的adapter函數來替換掉原生的模組(雖然一般不會這麼做,不過也算是一個松耦合擴展點)。
default.js檔案中,我們能夠看到相關的adapter選擇邏輯,也就是根據目前容器中特有的一些屬性和建構子來進行判斷。
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物件的封裝,我們在這裡就不過多進行介紹了,有興趣的同學可以自行閱讀,程式碼位於
adapters/xhr.js文件中。
dispatchRequest實作的HTTP請求發送模組,我們來看下axios是如何處理請求和回應攔截函數的。讓我們看下axios中請求的統一入口
request函數。
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; };這個函數是axios發送請求的入口,因為函數實作比較長,我就簡單說一下相關的設計想法:
chain是一个执行队列。这个队列的初始值,是一个带有config参数的Promise。
在chain执行队列中,插入了初始的发送请求的函数dispatchReqeust
和与之对应的undefined
。后面需要增加一个undefined
是因为在Promise中,需要一个success和一个fail的回调函数,这个从代码promise = promise.then(chain.shift(), chain.shift());
就能够看出来。因此,dispatchReqeust
和undefined
我们可以成为一对函数。
在chain执行队列中,发送请求的函数dispatchReqeust
是处于中间的位置。它的前面是请求拦截器,通过unshift
方法放入;它的后面是响应拦截器,通过push
放入。要注意的是,这些函数都是成对的放入,也就是一次放入两个。
通过上面的request
代码,我们大致知道了拦截器的使用方法。接下来,我们来看下如何取消一个HTTP请求。
取消请求相关的模块在Cancel/
文件夹中。让我们来看下相关的重点代码。
首先,让我们来看下元数据Cancel
类。它是用来记录取消状态一个类,具体代码如下:
function Cancel(message) { this.message = message; } Cancel.prototype.toString = function toString() { return 'Cancel' + (this.message ? ': ' + this.message : ''); }; Cancel.prototype.__CANCEL__ = true;
而在CancelToken类中,它通过传递一个Promise的方法来实现了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
文件中,有与之相对应的取消请求的代码:
if (config.cancelToken) { // 等待取消 config.cancelToken.promise.then(function onCanceled(cancel) { if (!request) { return; } request.abort(); reject(cancel); // 重置请求 request = null; }); }
结合上面的取消HTTP请求的示例和这些代码,我们来简单说下相关的实现逻辑:
在可能需要取消的请求中,我们初始化时调用了source方法,这个方法返回了一个CancelToken
类的实例A和一个函数cancel。
在source方法返回实例A中,初始化了一个在pending状态的promise。我们将整个实例A传递给axios后,这个promise被用于做取消请求的触发器。
当source方法返回的cancel方法被调用时,实例A中的promise状态由pending变成了fulfilled,立刻触发了then的回调函数,从而触发了axios的取消逻辑——request.abort()
。
在之前的章节中有提到过,axios在处理发送请求的dispatchRequest
函数时,没有当做一个特殊的函数来对待,而是采用一视同仁的方法,将其放在队列的中间位置,从而保证了队列处理的一致性,提高了代码的可阅读性。
在adapter的处理逻辑中,axios没有把http和xhr两个模块(一个用于Node.js发送请求,另一个则用于浏览器端发送请求)当成自身的模块直接在dispatchRequest
中直接饮用,而是通过配置的方法在default.js
文件中进行默认引入。这样既保证了两个模块间的低耦合性,同时又能够为今后用户需要自定义请求发送模块保留了余地。
在取消HTTP请求的逻辑中,axios巧妙的使用了一个Promise来作为触发器,将resolve函数通过callback中参数的形式传递到了外部。这样既能够保证内部逻辑的连贯性,也能够保证在需要进行取消请求时,不需要直接进行相关类的示例数据改动,最大程度上避免了侵入其他的模块。
本文对axios相关的使用方式、设计思路和实现方法进行了详细的介绍。读者能够通过上述文章,了解axios的设计思想,同时能够在axios的代码中,学习到关于模块封装和交互等相关的经验。
由于篇幅原因,本文仅针对axios的核心模块进行了分解和介绍,如果对其他代码有兴趣的同学,可以去GitHub进行查看。
相关推荐:
以上是axios原始碼解析如何實作一個HTTP請求庫的詳細內容。更多資訊請關注PHP中文網其他相關文章!