vue自2.0開始,vue-resource再也不做爲官方推薦的ajax方案,轉而推薦使用axios。vue
按照做者的原話來講:node
「Ajax 自己跟 Vue 並無什麼須要特別整合的地方,使用 fetch polyfill 或是 axios、superagent 等等均可以起到同等的效果,vue-resource 提供的價值和其維護成本相比並不划算,因此決定在不久之後取消對 vue-resource 的官方推薦。已有的用戶能夠繼續使用,但之後再也不把 vue-resource 做爲官方的 ajax 方案。」ios
除了維護成本方面的緣由,axios自己的優勢也使得它在一衆ajax異步請求的框架中脫穎而出,下面咱們經過分析部分axios的源碼來看看,是什麼讓axios成爲大多數人的選擇。git
GitHub上axios的主頁標註了它具備以下特性:github
Make XMLHttpRequests from the browserajax
Make http requests from node.js編程
Supports the Promise APIjson
Intercept request and responseaxios
Transform request and response data後端
Cancel requests
Automatic transforms for JSON data
Client side support for protecting against XSRF
咱們來一一分析。
因爲axios的這一特性,vue的服務端渲染對於axios簡直毫無抵抗力。 讓咱們一塊兒來讀讀源碼,看看它是如何實現的。
在axios/lib/core/dispatchRequest.js文件中暴露的dispatchRequest方法就是axios發送請求的方法,其中有一段代碼爲:
//定義適配器,判斷是在服務器環境仍是瀏覽器環境
var adapter = config.adapter || defaults.adapter;
return adapter(config).then(function onAdapterResolution(response) {
throwIfCancellationRequested(config);
// 處理返回的數據
response.data = transformData(
response.data,
response.headers,
config.transformResponse
);
return response;
}, function onAdapterRejection(reason) {
if (!isCancel(reason)) {
throwIfCancellationRequested(config);
// 處理失敗緣由
if (reason && reason.response) {
reason.response.data = transformData(
reason.response.data,
reason.response.headers,
config.transformResponse
);
}
}
return Promise.reject(reason);
});
};
這段代碼首先定義了一個適配器,而後返回了適配器處理後的內容。 若是沒有在傳入的配置參數中指定適配器,則取默認配置文件中定義的適配器,再讓咱們來看看默認文件/lib/defaults.js定義的適配器:
function getDefaultAdapter() {
var adapter;
if (typeof XMLHttpRequest !== 'undefined') {
//經過判斷XMLHttpRequest是否存在,來判斷是不是瀏覽器環境
adapter = require('./adapters/xhr');
} else if (typeof process !== 'undefined') {
//經過判斷process是否存在,來判斷是不是node環境
adapter = require('./adapters/http');
}
return adapter;
}
到這裏真相大白,XMLHttpRequest 是一個 API,它爲客戶端提供了在客戶端和服務器之間傳輸數據的功能;process 對象是一個 global (全局變量),提供有關信息,控制當前 Node.js 進程。原來做者是經過判斷XMLHttpRequest和process這兩個全局變量來判斷程序的運行環境的,從而在不一樣的環境提供不一樣的http請求模塊,實現客戶端和服務端程序的兼容。
同理,咱們在作ssr服務端渲染時,也可使用這個方法來判斷代碼當前的執行環境。
/**
* 處理一個請求
*
* @param config 請求的配置
*/
Axios.prototype.request = function request(config) {
// 若是是字符串,則直接賦值給配置的url屬性
if (typeof config === 'string') {
config = utils.merge({
url: arguments[0]
}, arguments[1]);
}
// 合併默認配置和配置參數
config = utils.merge(defaults, this.defaults, { method: 'get' }, config);
config.method = config.method.toLowerCase();
// 鏈接攔截器中間件
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());
}
//返回promise對象
return promise;
};
這一段是axios請求總體流程的核心方法,能夠看到請求返回的是一個promise對象。這樣可讓咱們的異步請求自然的支持promise,方便咱們對於異步的處理。
依然是上面的核心流程代碼,在設置好請求參數後,做者定義了一個chain數組,同時放入了dispatchRequest, undefined這兩個元素對應promise的resolve和reject方法,以後將請求攔截器的成功和失敗處理依次壓入chain數組頭部,將返回攔截器的成功和失敗處理依次推入chain數組尾部。 最後循環取出chain數組,先依次取出chain數組中成對的請求攔截處理方法,promise執行,而後取出最初定義的dispatchRequest, undefined這兩個元素執行請求,最後依次取出chain數組中成對的返回攔截器。
它的流程能夠概括爲
resolve(request interceptor fulfilled N),reject(request interceptor rejected N)
-> ...
-> resolve(request interceptor fulfilled 0),reject(request interceptor rejected 0)
-> resolve(dispatchRequest ),reject(undefined)
-> resolve(response interceptor fulfilled 0),reject(response interceptor rejected 0)
-> ...
-> resolve(response interceptor fulfilled N),reject(response interceptor rejected N)
在axios/lib/core/dispatchRequest.js文件中暴露的核心方法dispatchRequest中,有這樣兩段:
// 轉換請求的數據
config.data = transformData(
config.data,
config.headers,
config.transformRequest
);
// 轉換返回的數據
response.data = transformData(
response.data,
response.headers,
config.transformResponse
);
axios經過設置transformResponse,可自動轉換請求返回的json數據
transformResponse: [function transformResponse(data) {
/*eslint no-param-reassign:0*/
if (typeof data === 'string') {
try {
data = JSON.parse(data);
} catch (e) { /* Ignore */ }
}
return data;
}],
文檔上給了兩種示例:
第一種:
var CancelToken = axios.CancelToken;
var source = CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token
}).catch(function(thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// 處理錯誤
}
});
// 取消請求(message 參數是可選的)
source.cancel('取消請求');
第二種:
var CancelToken = axios.CancelToken;
var cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// executor 函數接收一個 cancel 函數做爲參數
cancel = c;
})
});
// 取消請求
cancel();
這兩種方法均可以取消發出的請求。先看具體的流程:
執行 cancel 方法 -> CancelToken.promise獲取到resolve -> request.abort(); -> reject(cancel);
下面來看源碼是如何實現的。
在xhr.js或http.js中都有這樣一段,下面取自/lib/adapters/xhr.js
if (config.cancelToken) {
// 處理取消請求的方法
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
request.abort();
reject(cancel);
// 清空請求
request = null;
});
}
在請求時若是設置了cancelToken參數,就會監聽來自cancelToken的promise,一旦來自cancelToken的promise被觸發,就會執行取消請求的流程。
cancelToken的具體實現爲:
/**
* 能夠進行取消請求操做的對象
*
* @param {Function} executor 具體的執行方法.
*/
function CancelToken(executor) {
//判斷executor是一個可執行的函數
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.');
}
//定義一個promise的resolve回調
var resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
var token = this;
//executor的參數爲取消請求時須要執行的cancel函數
executor(function cancel(message) {
if (token.reason) {
// 已經取消了請求
return;
}
token.reason = new Cancel(message);
//觸發promise的resolve
resolvePromise(token.reason);
});
}
就是在上面的代碼裏,CancelToken會給本身添加一個promise屬性,一旦cancel方法被觸發就會執行取消請求的流程。
利用這個方法,一方面能夠在按鈕的重複點擊方面大顯身手,另外一方面能夠在數據的獲取方面直接獲取最新的數據。
先來了解一下XSRF,如下內容來自維基百科。
XSRF跨站請求僞造(Cross-site request forgery),是一種挾制用戶在當前已登陸的Web應用程序上執行非本意的操做的攻擊方法。
舉個例子:
假如一家網站執行轉帳的操做URL地址以下:
http://www.examplebank.com/withdraw?account=AccoutName&psd=1000&for=PayeeName
那麼,一個惡意攻擊者能夠在另外一個網站上放置以下代碼:
<img src="http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman">
若是有帳戶名爲Alice的用戶訪問了惡意站點,而她以前剛訪問過銀行不久,登陸信息還沒有過時,那麼她就會損失1000資金。
-------我是分割線,以上內容來自維基百科-------
axios是如何作的?
// 添加 xsrf 請求頭
// 只在標準瀏覽器環境中才會起做用
if (utils.isStandardBrowserEnv()) {
var cookies = require('./../helpers/cookies');
// 添加 xsrf 請求頭
var xsrfValue = (config.withCredentials || isURLSameOrigin(config.url)) && config.xsrfCookieName ?
cookies.read(config.xsrfCookieName) :
undefined;
if (xsrfValue) {
requestHeaders[config.xsrfHeaderName] = xsrfValue;
}
}
首先,axios會檢查是不是標準的瀏覽器環境,而後在標準的瀏覽器環境中判斷,若是設置了跨域請求時須要憑證且請求的域名和頁面的域名相同時,讀取cookie中xsrf token 的值,並設置到承載 xsrf token 的值的 HTTP 頭中。
若是在標準瀏覽器環境則執行/lib/adapters/xhr.js,axios不支持proxy;若是在node環境運行,用/lib/adapters/http.js,支持proxy。
在組內的一個項目中,使用了vue的ssr服務端渲染的技術,其中ajax方案就是採用了axios。因爲ssr是在服務端請求,所以在開發、測試、上線的過程當中須要相應的host環境。例如在開發過程當中,要麼須要在程序的請求地址中寫上ip地址替代域名,要麼須要設置電腦的host文件,改變域名映射的ip地址。而在測試環境中,一樣須要更改代碼或者測試環境的host文件。這樣一來,若是是改代碼的方案則影響了代碼的穩定性,每一次部署都須要修改代碼;若是是修改host文件,則會影響環境的一致性,假如環境還部署了其餘的服務,還會影響其餘服務的測試。所以咱們組內的男神便使用了axios的proxy功能,輕鬆的解決了這一問題。
//判斷當前的部署環境
const isDev = process.env.NODE_ENV !== 'production'
if(isDev){
let proxy = null;
//若是不是線上環境,且配置了代理地址則進行代理的設置,devHost是具體的ip配置
if(devHost.https){
proxy = {
host: devHost.https,
port: 443
};
Axios.defaults.proxy = proxy;
}else if(devHost.http){
proxy = {
host: devHost.http,
port: 80
};
Axios.defaults.proxy = proxy;
}else {
//do nothing
}
}
而在axios的源碼/lib/adapters/http.js中,則是如此實現代理的:
//若是設置了代理
if (proxy) {
//取代理的域名爲請求的域名
options.hostname = proxy.host;
options.host = proxy.host;
options.headers.host = parsed.hostname + (parsed.port ? ':' + parsed.port : '');
options.port = proxy.port;
options.path = protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path;
// Basic proxy authorization
if (proxy.auth) {
var base64 = new Buffer(proxy.auth.username + ':' + proxy.auth.password, 'utf8').toString('base64');
options.headers['Proxy-Authorization'] = 'Basic ' + base64;
}
}
上面基於源碼具體分析了axios的各項特性,下面再來說一講咱們在具體使用時的一些二次封裝。因爲axios使用get方式設置參數時,都須要使用params的方式,例如:
axios.get('/user', {
params: {
ID: 12345
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
而以前使用vue-resource則習慣直接寫上參數,形如:
axios.get('/user',
{
ID: 12345
}
)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
所以,對於組內的axios統一加了一層封裝,承接以前的使用習慣:
let get = Axios.get;
/**
* 對原方法的get作一層裝飾,能夠傳參時沒必要寫params參數,直接傳遞參數對象,同時對已有的params寫法兼容
* @param args 參數 url config
* @returns {*}
*/
Axios.get = (...args) => {
let param = args[1];
//若是以參數方式傳遞query,同時不存在axios須要的key:params,則爲它添加
if (param && !param.hasOwnProperty('params')) {
args[1] = {
params: param
}
}
return get(...args)
}
這裏須要注意的是,要確保在提交到服務端的query參數中不包含‘params’字段,否則仍是要使用默認的參數格式。
而對於post方式,則作了以下封裝:
let post = Axios.post;
/**
* axios的post請求默認會依據數據類型設置請求頭,可是目先後臺沒有識別json,所以統一將請求的數據設置爲x-www-form-urlencoded須要的字符串格式
* @param args 參數 url data config
* @returns {*}
*/
Axios.post = (...args) => {
let data = args[1];
//判斷是對象就轉化爲字符串
if (data && typeof data === 'object') {
args[1] = qs.stringify(data)
}
return post(...args)
}
axios使用方便,功能齊備強大,其中的一些編程思想也很不入俗套,是一款先後端通用的ajax請求框架,目前在github上已經有接近36K的贊,其優秀程度可見一斑。本文經過他的一些特性,分析了部分源碼,旨在可以在使用它的同時,更加懂得它。
——————————————————
長按二維碼,關注大轉轉FE