axios 核心源碼實現原理

axios 核心源碼實現原理

1. Interceptors 攔截器

axios 官網中對 Interceptors 的使用方法如下: 用戶可以通過 then 方法為請求添加回調,而攔截器中的回調將在 then 中的回調之前執行:

// 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實例添加一個Interceptors:

const instance = axios.create();
instance.interceptors.request.use(function () {/*...*/});

從其中的使用方法,我們可以知道 request 攔截器需要在請求之前執行,response 攔截器需要再請求之後執行。我們看一下axios的實現方式: 首先axios為攔截器定義了一個管理中心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);
}
});
};

其次,當實例化Axios時,分別創建一個request 和一個response攔截器:

function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}

然後我們需要通過use來分別添加攔截器時,便將我們定義的resolve和reject收入對應的request和response中。在此之前,我們需要對Promise有一個簡單的瞭解:

Promise then 方法返回的是一個新的 Promise 實例(注意,不是原來那個 Promise 實例)。因此可以採用鏈式寫法,即 then 方法後面再調用另一個 then 方法。

getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {

// ...
});

上面的代碼使用then方法,依次指定了兩個回調函數。第一個回調函數完成以後,會將返回結果作為參數,傳入第二個回調函數。採用鏈式的then,可以指定一組按照次序調用的回調函數。這時,前一個回調函數,有可能返回的還是一個Promise對象(即有異步操作),這時後一個回調函數,就會等待該Promise對象的狀態發生變化,才會被調用。

getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function funcA(comments) {
console.log("resolved: ", comments);
}, function funcB(err){
console.log("rejected: ", err);
});

上面代碼中,第一個then方法指定的回調函數,返回的是另一個Promise對象。這時,第二個then方法指定的回調函數,就會等待這個新的Promise對象狀態發生變化。如果變為resolved,就調用funcA,如果狀態變為rejected,就調用funcB。

接下來,看看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;
};

首先定義了一個數組調用鏈chain,然後通過unshift將 request interceptors推入數組頭部,將response interceptors推入數組尾部。最後通過while這樣一個循環執行promise.then來達到鏈式調用。 來看一張圖:

axios 核心源碼實現原理

通過巧妙的利用unshift、push、shift等數組隊列、棧方法,實現了請求攔截、執行請求、響應攔截的流程設定,注意無論是請求攔截還是響應攔截,越先添加的攔截器總是越“貼近”執行請求本身。

2. 適配 nodejs 和 瀏覽器環境

我們知道 axios 可以用來發送瀏覽器請求,也可以用於 nodejs 發送服務端請求,主要是因為 axios 內部實現了一個請求適配器adapter:

// 發送請求方法
function dispatchRequest (config) {
...
var adapter = config.adapter || defaults.adapter;
return adapter(config).then(function onAdapterResolution(response) { ... }, function onAdapterRejection(reason) {...})
}

看一下 adapter 具體實現:

function getDefaultAdapter() {
var adapter;
// 如果存在 process 且 process 變量是一個對象,則判斷為 nodejs 環境,封裝 http 來請求
// Only Node.JS has a process variable that is of [[Class]] process
if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
// For node use HTTP adapter
adapter = require('./adapters/http');
// 如果存在 XMLHttpRequest 對象,則調用 XMLHttpRequest 來請求
} else if (typeof XMLHttpRequest !== 'undefined') {
// For browsers use XHR adapter
adapter = require('./adapters/xhr');
}

return adapter;
}

有了這些,我們可以大膽猜想一下,我們是不是可以實現不同的adapter來達到跨平臺支持的目的,只需要修改底層請求方法便可。

3. 自動轉換JSON數據

在默認情況下,axios將會自動的將傳入的data對象序列化為JSON字符串,將響應數據中的JSON字符串轉換為JavaScript對象。這是一個非常實用的功能,但實現起來非常簡單:

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();
}
// 這裡對 object 做了轉換
if (utils.isObject(data)) {
setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
return JSON.stringify(data);
}

return data;
}],
transformResponse: [function transformResponse(data) {
// 嘗試轉換 string -> json
if (typeof data === 'string') {
try {
data = JSON.parse(data);
} catch (e) { /* Ignore */ }
}
return data;
}]
...
}

4. 支持客戶端 XSRF 攻擊防護

XSRF 攻擊,即“跨站請求偽造”(Cross Site Request Forgery)攻擊。通過竊取用戶cookie,讓用戶在本機(即擁有身份 cookie 的瀏覽器端)發起用戶所不知道的請求。防護XSRF攻擊的一種方法是設置特殊的 xsrf token,axios實現了對這種方法的支持:

// 設置 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跨域或者同源,首先會從cookie中讀取定義的token

// 如果存在 xsrfValue 會將讀取到的 xsrfValue 攜帶進入 requestHeaders 中
var xsrfValue = (config.withCredentials || isURLSameOrigin(config.url)) && config.xsrfCookieName ?
cookies.read(config.xsrfCookieName) :
undefined;
if (xsrfValue) {
requestHeaders[config.xsrfHeaderName] = xsrfValue;
}
}
});
};

總結

我們可以看到,axios在實現封裝網絡請求所需的各項擴展功能時,都是使用的最樸素JavaScript源生方法,並且總是通過簡單的鏈式then方法將這些功能與核心的promise對象關聯。此外各種優化性能的方法,也都是採用的很基本的原理。

axios 核心源碼實現原理

最後分享兩個最近在讀的掘金小冊,大牛果然還是人家的大牛,現階段膜拜還是要有的,我讀了感覺收貨很大,推薦給我的讀者小夥伴們。好看不貴還實惠(其實也是我的),大概就是人家掘金小冊的定位了吧

axios 核心源碼實現原理

參考

https://github.com/muwoo/blogs/blob/master/src/axios/1.md

https://zhuanlan.zhihu.com/p/28396592


分享到:


相關文章: