詳解axios跨端架構(gòu)是如何實現(xiàn)的
介紹
我們都知道,axios 是是一個跨平臺請求方案,在瀏覽器端采用 XMLHttpRequest API 進行封裝,而在 Node.js 端則采用 http/https 模塊進行封裝。axios 內(nèi)部采用適配器模式將二者合二為一,在隱藏了底層的實現(xiàn)的同時,又對外開放了一套統(tǒng)一的開放接口。
那么本文,我們將來探討這個話題:axios 的跨端架構(gòu)是如何實現(xiàn)的?
從 axios 發(fā)送請求說起
我們先來看看 axios 是如何發(fā)送請求的。
// 發(fā)送一個 GET 請求 axios({ method: 'get', url: 'https://jsonplaceholder.typicode.com/comments' params: { postId: 1 } }) // 發(fā)送一個 POST 請求 axios({ method: 'post' url: 'https://jsonplaceholder.typicode.com/posts', data: { title: 'foo', body: 'bar', userId: 1, } })
dispatchRequest() 方法
當使用 axios 請求時,實際上內(nèi)部是由 Axios 實例的 .request()
方法處理的。
// /v1.6.8/lib/core/Axios.js#L38 async request(configOrUrl, config) { try { return await this._request(configOrUrl, config); } catch (err) {} }
而 ._request() 方法內(nèi)部會先將 configOrUrl, config 2 個參數(shù)處理成 config 參數(shù)。
// /v1.6.8/lib/core/Axios.js#L62 _request(configOrUrl, config) { if (typeof configOrUrl === 'string') { config = config || {}; config.url = configOrUrl; } else { config = configOrUrl || {}; } // ... }
這里是為了同時兼容下面 2 種調(diào)用方法。
// 調(diào)用方式一 axios('https://jsonplaceholder.typicode.com/posts/1') // 調(diào)用方式二 axios({ method: 'get', url: 'https://jsonplaceholder.typicode.com/posts/1' })
當然,這不是重點。在 ._request() 方法內(nèi)部請求最終會交由 dispatchRequest() 處理。
// /v1.6.8/lib/core/Axios.js#L169-L173 try { promise = dispatchRequest.call(this, newConfig); } catch (error) { return Promise.reject(error); }
dispatchRequest() 是實際調(diào)用請求的地方,而實際調(diào)用是采用 XMLHttpRequest API(瀏覽器)還是http/https 模塊(Node.js),則需要進一步查看。
// /v1.6.8/lib/core/dispatchRequest.js#L34 export default function dispatchRequest(config) { /* ... */ }
dispatchRequest() 接收的是上一步合并之后的 config 參數(shù),有了這個參數(shù)我們就可以發(fā)送請求了。
跨端適配實現(xiàn)
// /v1.6.8/lib/core/dispatchRequest.js#L49 const adapter = adapters.getAdapter(config.adapter || defaults.adapter);
這里就是我們所說的 axios 內(nèi)部所使用的適配器模式了。
axios 支持從外出傳入 adapter
參數(shù)支持自定義請求能力的實現(xiàn),不過很少使用。大部分請求下,我們都是使用內(nèi)置的適配器實現(xiàn)。
defaults.adapter
defaults.adapter
的值如下:
// /v1.6.8/lib/defaults/index.js#L40 adapter: ['xhr', 'http'],
adapters.getAdapter(['xhr', 'http'])
又是在做什么事情呢?
適配器實現(xiàn)
首先,adapters 位于 lib/adapters/adapters.js。
所屬的目錄結(jié)構(gòu)如下:
可以看到針對瀏覽器和 Node.js 2 個環(huán)境的適配支持:http.js、xhr.js。
adapters 的實現(xiàn)如下。
首先,將內(nèi)置的 2 個適配文件引入。
// /v1.6.8/lib/adapters/adapters.js#L2-L9 import httpAdapter from './http.js'; import xhrAdapter from './xhr.js'; const knownAdapters = { http: httpAdapter, xhr: xhrAdapter }
knownAdapters 的屬性名正好是和 defaults.adapter 的值 ['xhr', 'http'] 是一一對應的。
而 adapters.getAdapter(['xhr', 'http']) 的實現(xiàn)是這樣的:
// /v1.6.8/lib/adapters/adapters.js#L27-L75 export default { getAdapter: (adapters) => { // 1) adapters = Array.isArray(adapters) ? adapters : [adapters]; let nameOrAdapter; let adapter; // 2) for (let i = 0; i < adapters.length; i++) { nameOrAdapter = adapters[i]; adapter = nameOrAdapter; // 3) if (!isResolvedHandle(nameOrAdapter)) { adapter = knownAdapters[String(nameOrAdapter).toLowerCase()]; } if (adapter) { break; } } // 4) if (!adapter) { throw new AxiosError( `There is no suitable adapter to dispatch the request `, 'ERR_NOT_SUPPORT' ); } return adapter; } }
內(nèi)容比較長,我們會按照代碼標準的序號分 4 個部分來講。
1)、這里是為了兼容調(diào)用 axios() 時傳入 adapter 參數(shù)的情況。
// `adapter` allows custom handling of requests which makes testing easier. // Return a promise and supply a valid response (see lib/adapters/README.md). adapter: function (config) { /* ... */ },
因為接下來 adapters 是作為數(shù)組處理,所以這種場景下,我們將 adapter 封裝成數(shù)組 [adapters]
。
// /v1.6.8/lib/adapters/adapters.js#L28 adapters = Array.isArray(adapters) ? adapters : [adapters];
2)、接下來,就是遍歷 adapters 找到要用的那個適配器。
到目前為止,adapters[i](也就是下面的 nameOrAdapter)既可能是字符串('xhr'
、'http'
),也可能是函數(shù)(function (config) {}
)。
// /v1.6.8/lib/adapters/adapters.js#L37 let nameOrAdapter = adapters[i]; adapter = nameOrAdapter;
3)、那么,我們還要檢查 nameOrAdapter 的類型。
// /v1.6.8/lib/adapters/adapters.js#L42-L48 if (!isResolvedHandle(nameOrAdapter)) { adapter = knownAdapters[(id = String(nameOrAdapter)).toLowerCase()]; }
isResolvedHandle() 是一個工具函數(shù),其目的是為了判斷是否要從 knownAdapters 獲取適配器。
// /v1.6.8/lib/adapters/adapters.js#L24 const isResolvedHandle = (adapter) => typeof adapter === 'function' || adapter === null || adapter === false;
簡單理解,只有 adapter 是字符串的情況('xhr'
或 'http'
),isResolvedHandle(nameOrAdapter) 才返回 false,才從 knownAdapters 獲得適配器。
typeof adapter === 'function' || adapter === null 這個判斷條件我們?nèi)菀桌斫猓@是為了排除自定義 adapter 參數(shù)(傳入函數(shù)或 null)的情況。
而 adapter === false 又是對應什么情況呢?
那是因為我們的代碼只可能是在瀏覽器或 Node.js 環(huán)境下運行。這個時候 httpAdapter 和 xhrAdapter 具體返回是有差異的。
// /v1.6.8/lib/adapters/xhr.js#L48 const isXHRAdapterSupported = typeof XMLHttpRequest !== 'undefined'; export default isXHRAdapterSupported && function (config) {/* ...*/} // /v1.6.8/lib/adapters/http.js#L160 const isHttpAdapterSupported = typeof process !== 'undefined' && utils.kindOf(process) === 'process'; export default isHttpAdapterSupported && function httpAdapter(config) {/* ... */}
也就是說:在瀏覽器環(huán)境 httpAdapter 返回 false,xhrAdapter 返回函數(shù);在 Node.js 環(huán)境 xhrAdapter 返回 false,httpAdapter 返回函數(shù)。
因此,一旦 isResolvedHandle() 邏輯執(zhí)行完成后。
if (!isResolvedHandle(nameOrAdapter)) {/* ... */}
會檢查 adapter 變量的值,一旦有值(非 false
)就說明找到適配器了,結(jié)束遍歷。
if (adapter) { break; }
4)、最終在返回適配器前做空檢查
// 4) if (!adapter) { throw new AxiosError( `There is no suitable adapter to dispatch the request `, 'ERR_NOT_SUPPORT' ); } return adapter;
如此,就完成了跨端架構(gòu)的實現(xiàn)。
總結(jié)
本文我們講述了 axios 的跨端架構(gòu)原理。axios 內(nèi)部實際發(fā)出請求是通過 dispatchRequest() 方法處理的,再往里看則是通過適配器模式取得適應于當前環(huán)境的適配器函數(shù)。
axios 內(nèi)置了 2 個適配器支持:httpAdapter 和 xhrAdapter。httpAdapter 是 Node.js 環(huán)境實現(xiàn),通過 http/https 模塊;xhrAdapter 這是瀏覽器環(huán)境實現(xiàn),通過 XMLHttpRequest API 實現(xiàn)。Node.js 環(huán)境 xhrAdapter 返回 false,瀏覽器環(huán)境 httpAdapter 返回 false——這樣總是能返回正確的適配器。
以上就是詳解axios跨端架構(gòu)是如何實現(xiàn)的的詳細內(nèi)容,更多關(guān)于axios跨端架構(gòu)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
window.event.srcElement 得到事件源對象
window.event.srcElement 得到事件源對象代碼,大家可以參考腳本之家以前發(fā)的代碼,多瀏覽兼容的。2009-05-05JavaScript 對象深入學習總結(jié)(經(jīng)典)
JavaScript中,除了五種原始類型(即數(shù)字,字符串,布爾值,null,undefined)之外的都是對象了,所以,不把對象學明白怎么繼續(xù)往下學習呢?本篇文章給大家分享javascript對象深入學習總結(jié),小伙伴們跟著小編一起深入學習吧2015-09-09JavaScript回調(diào)函數(shù)callback用法解析
這篇文章主要介紹了JavaScript回調(diào)函數(shù)callback用法解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-01-01