1. Interceptors 攔截器
axios 官網(wǎng)中對(duì)Interceptors 的使用方法如下: 用戶可以通過(guò) then 方法為請(qǐng)求添加回調(diào),而攔截器中的回調(diào)將在 then 中的回調(diào)之前執(zhí)行:
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Do something with response data
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});
之后你可能需要能移除 Interceptors :
const myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);
也可以為axios實(shí)例添加一個(gè)Interceptors:
const instance = axios.create();
instance.interceptors.request.use(function () {/*...*/});
從其中的使用方法,我們可以知道 request 攔截器需要在請(qǐng)求之前執(zhí)行,response 攔截器需要再請(qǐng)求之后執(zhí)行。我們看一下axios的實(shí)現(xiàn)方式: 首先axios為攔截器定義了一個(gè)管理中心InterceptorManager:
function InterceptorManager() {
this.handlers = [];
}
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
};
2.當(dāng)實(shí)例化Axios時(shí),分別創(chuàng)建一個(gè)request 和一個(gè)response攔截器:
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
然后我們需要通過(guò)use來(lái)分別添加攔截器時(shí),便將我們定義的resolve和reject收入對(duì)應(yīng)的request和response中。在此之前,我們需要對(duì)Promise有一個(gè)簡(jiǎn)單的了解:
Promise then 方法返回的是一個(gè)新的 Promise 實(shí)例(注意,不是原來(lái)那個(gè) Promise 實(shí)例)。因此可以采用鏈?zhǔn)綄?xiě)法,即 then 方法后面再調(diào)用另一個(gè) then 方法。
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// ...
});
上面的代碼使用then方法,依次指定了兩個(gè)回調(diào)函數(shù)。第一個(gè)回調(diào)函數(shù)完成以后,會(huì)將返回結(jié)果作為參數(shù),傳入第二個(gè)回調(diào)函數(shù)。采用鏈?zhǔn)降膖hen,可以指定一組按照次序調(diào)用的回調(diào)函數(shù)。這時(shí),前一個(gè)回調(diào)函數(shù),有可能返回的還是一個(gè)Promise對(duì)象(即有異步操作),這時(shí)后一個(gè)回調(diào)函數(shù),就會(huì)等待該P(yáng)romise對(duì)象的狀態(tài)發(fā)生變化,才會(huì)被調(diào)用。
接下來(lái),看看request方法是如何實(shí)現(xiàn)攔截器功能的:
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ù)組調(diào)用鏈chain,然后通過(guò)unshift將 request interceptors推入數(shù)組頭部,將response interceptors推入數(shù)組尾部。最后通過(guò)while這樣一個(gè)循環(huán)執(zhí)行promise.then來(lái)達(dá)到鏈?zhǔn)秸{(diào)用。 來(lái)看一張圖

通過(guò)巧妙的利用unshift、push、shift等數(shù)組隊(duì)列、棧方法,實(shí)現(xiàn)了請(qǐng)求攔截、執(zhí)行請(qǐng)求、響應(yīng)攔截的流程設(shè)定,注意無(wú)論是請(qǐng)求攔截還是響應(yīng)攔截,越先添加的攔截器總是越“貼近”執(zhí)行請(qǐng)求本身。
3. 自動(dòng)轉(zhuǎn)換JSON數(shù)據(jù)
在默認(rèn)情況下,axios將會(huì)自動(dòng)的將傳入的data對(duì)象序列化為JSON字符串,將響應(yīng)數(shù)據(jù)中的JSON字符串轉(zhuǎn)換為JavaScript對(duì)象。這是一個(gè)非常實(shí)用的功能,但實(shí)現(xiàn)起來(lái)非常簡(jiǎn)單:
var defaults = {
...
transformRequest: [function transformRequest(data, headers) {
normalizeHeaderName(headers, 'Content-Type');
if (utils.isFormData(data) ||
utils.isArrayBuffer(data) ||
utils.isBuffer(data) ||
utils.isStream(data) ||
utils.isFile(data) ||
utils.isBlob(data)
) {
return data;
}
if (utils.isArrayBufferView(data)) {
return data.buffer;
}
if (utils.isURLSearchParams(data)) {
setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
return data.toString();
}
// 這里對(duì) object 做了轉(zhuǎn)換
if (utils.isObject(data)) {
setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
return JSON.stringify(data);
}
return data;
}],
transformResponse: [function transformResponse(data) {
// 嘗試轉(zhuǎn)換 string -> json
if (typeof data === 'string') {
try {
data = JSON.parse(data);
} catch (e) { /* Ignore */ }
}
return data;
}]
...
}
4. 支持客戶端 XSRF 攻擊防護(hù)
XSRF 攻擊,即“跨站請(qǐng)求偽造”(Cross Site Request Forgery)攻擊。通過(guò)竊取用戶cookie,讓用戶在本機(jī)(即擁有身份 cookie 的瀏覽器端)發(fā)起用戶所不知道的請(qǐng)求。防護(hù)XSRF攻擊的一種方法是設(shè)置特殊的 xsrf token,axios實(shí)現(xiàn)了對(duì)這種方法的支持:
// 設(shè)置 xsrf 的 cookie 字段名和 header 字段名
{
// `xsrfCookieName` is the name of the cookie to use as a value for xsrf token
xsrfCookieName: 'XSRF-TOKEN', // default
// `xsrfHeaderName` is the name of the http header that carries the xsrf token value
xsrfHeaderName: 'X-XSRF-TOKEN', // default
}
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
...
// Add xsrf header
// This is only done if running in a standard browser environment.
// Specifically not if we're in a web worker, or react-native.
if (utils.isStandardBrowserEnv()) {
var cookies = require('./../helpers/cookies');
// Add xsrf header
// 如果允許cookie跨域或者同源,首先會(huì)從cookie中讀取定義的token
// 如果存在 xsrfValue 會(huì)將讀取到的 xsrfValue 攜帶進(jìn)入 requestHeaders 中
var xsrfValue = (config.withCredentials || isURLSameOrigin(config.url)) && config.xsrfCookieName ?
cookies.read(config.xsrfCookieName) :
undefined;
if (xsrfValue) {
requestHeaders[config.xsrfHeaderName] = xsrfValue;
}
}
});
};