AXIOS原始碼重點痛點分析

來源:互聯網
上載者:User

標籤:roc   uil   end   arguments   node   main   typeof   tco   name   

  • 摘要

vue使用axios進行http通訊,類似jquery/ajax的作用,類似angular http的作用,axios功能強大,使用方便,是一個優秀的http軟體,本文旨在分享axios原始碼重點痛點分析,無意從頭到尾詳細分析原始碼的各個細節。

 

  • axios的封裝

axios做了複雜深奧的封裝,不同於普通的對象/執行個體方法。

debug看axios.get()代碼是:
bind.js:
module.exports = function bind(fn, thisArg) {
return function wrap() { //axios是這個方法
var args = new Array(arguments.length);
for (var i = 0; i < args.length; i++) {
args[i] = arguments[i];
}
return fn.apply(thisArg, args);
};

再看axios入口檔案axios.js代碼:
function createInstance(defaultConfig) {
var context = new Axios(defaultConfig); //這是axios執行個體
var instance = bind(Axios.prototype.request, context); //bind返回wrap方法,而不是axios執行個體
// Copy axios.prototype to instance
utils.extend(instance, Axios.prototype, context); //把Axios靜態對象的屬性方法複製到instance,由於要改變屬性方法中的this指標,因此不能簡單複製屬性方法,
而是要寫成warp方法,此方法返回fn.apply(context,args),那麼調用屬性方法時,就是執行wrap方法,就是執行
fn.apply然後返回,也就是給方法fn加一層封裝以便調整指標,否則屬性方法就是fn很簡單。
// Copy context to instance
utils.extend(instance, context); //把axios執行個體的屬性方法複製到instance
return instance;
}
var axios = createInstance(defaults); //defaults對象是預設配置資料,用var defaults=require(‘./defaults‘)擷取
module.exports = axios;

在bind看fn是:
Axios.prototype.request
Axios.prototype[method]

fn就是Axios靜態對象的屬性方法:request,get, get方法代碼如下:

utils.forEach([‘delete‘, ‘get‘, ‘head‘], function forEachMethodNoData(method) {
Axios.prototype[method] = function(url, config) {
return this.request(utils.merge(config || {}, { //this在調用這個方法時已經被調整為Axios執行個體
method: method,
url: url
}));
};

就是執行Axios執行個體的request方法。

在extend代碼用debug看Axios.prototype有如下屬性方法:
request
delete
get
head
post
put
patch
defaults
interceptors

這些屬性方法複製到axios,axios就有這些屬性方法,因此可以寫axios.get調用其方法,但debug看axios是一個wrap方法,
是一個建構函式,也是一個對象,是一個特殊的對象,不能new axios()執行個體化,看axios看不見屬性,但看axios.get是有的,
看axios.abc是沒有的。
因此axios是一個很特殊的建構函式/對象,其屬性方法也是特殊構造的,就是wrap()函數,代碼就是fn.apply(),因此
調用axios.get時,確實是調用axios對象的get屬性方法,就是調用Axios.prototype.get方法,Axios是原型,axios是一個
instance,instance繼承原型,在調用原型方法時把this調整為原型執行個體new Axios()。
這個編程方法很進階,照理說,一般都是new Axios(),然後調用執行個體的屬性方法,很簡單,但它不是這樣設計,它是設計
一個特殊的對象axios,再把Axios做為原型複製到axios,那麼axios就是Axios原型的一個instance,再把方法中的this調整為
new Axios執行個體,這樣當調用axios的屬性方法時,相當於是調用new Axios執行個體的屬性方法。
先定義一個標準的原型Axios,再如此建立一個instance(axios),匪夷所思,這樣設計是為了層次化,把原型和instance
分開。

axios其實就是Axios,只是axios本身和屬性方法做了一個封裝。

 

  • axios重點原始碼分析


用迴圈方法可以看到axios的所有屬性名稱:
for(var key in axios){
console.log(key);
};
request
delete
get
head
post
put
patch
defaults
interceptors
Axios
create
Cancel
CancelToken
isCancel
all
spread
default

因此axios.defaults是訪問/修改axios的defaults屬性,也是從Axios.prototype複製來的,複製屬性/訪問屬性沒有this問題。

調用axios.get實際上就是調用Axios.prototype.get原型方法,方法中的this代表new Axios執行個體:

utils.forEach([‘delete‘, ‘get‘, ‘head‘], function forEachMethodNoData(method) {
Axios.prototype[method] = function(url, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url
}));

get方法就是調用request方法,傳參method:‘get‘而已,因此axios.get代碼實際上是從request代碼開始:

Axios.prototype.request = function request(config) {
config = utils.merge(defaults, this.defaults, { method: ‘get‘ }, config);
//axios.defaults會合并到config,config本來只是傳入的資料,這之後增加了data,baseUrl等屬性

config.url = combineURLs(config.baseURL, config.url); //拼接url,調用get時如果輸入絕對url路徑就不用再拼接

promise = promise.then(http攔截函數/dispatchrequest); //這裡面http過程有嵌套promise
return promise; //for axios.get().then(callback)

function dispatchRequest(config) {
config.headers = utils.merge(
config.headers.common || {},
config.headers[config.method] || {},
config.headers || {}
);
可以看到axios.headers.common或axios.headers["get"]這樣寫法的都做為header屬性配置都會合并到headers,因此像axios.defaults.headers.common["access_token"] = "123456"這種寫法結果是一樣的。

return adapter(config).then(function onAdapterResolution(response) {
return response;
}, function onAdapterRejection(reason) {
return Promise.reject(reason);

function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
var requestData = config.data;
var requestHeaders = config.headers;
utils.forEach(requestHeaders, function setRequestHeader(val, key) {
request.setRequestHeader(key, val);

在adapter底層調setRequestHeader原生方法設定http request header屬性,看http報文頭是:

Request Headers:
Accept:application/json, text/plain, */* //這個屬性是axios設定的預設屬性
Accept-Encoding:gzip, deflate, br
Accept-Language:zh-CN,zh;q=0.8
access_token:123456
Authorization:token 4b26496037665e0c4d308ec682b6e7fb
test:hello

其中一個屬性是在http.js中設定的global屬性(每個http請求都有效):
axios.defaults.headers["access_token"] = "123456";

對於特定的http請求也可以傳一個json object {}來設定http/header屬性,類似jquery ajax寫法:
axios.get(url,{
headers:{
test:"hello"
},
params:{
ID:"123456" //由於是get請求,此參數會自動附加在url中以query string形式傳遞,與直接在url寫?ID=123456一樣。
}
)

有些屬性是瀏覽器進行http通訊時使用的預設屬性。



因此http參數配置,一是可以在http.js寫global配置,二是可以在調用get方法時直接寫一個{屬性名稱:屬性值}傳進去即可,
axios約定的屬性名稱可以參考api,也可以直接看原代碼,寫參數類似jquery ajax,都是用json object形式,比如:
{
headers: {‘X-Requested-With‘: ‘XMLHttpRequest‘},
params: { //做為query string參數
ID: 12345
},
data: { // post data
firstName: ‘Fred‘
},
timeout: 1000, //http request timeout可以每次請求時配置,預設是5s

}

調用寫法與jquery ajax類似:axios.get(url,{}),原代碼中config資料就是傳入的{}。


原代碼中有:
path: buildURL(parsed.path, config.params, config.paramsSerializer).replace(/^\?/, ‘‘),

這是構造url時使用配置object的params屬性來構造?a=b這樣的參數。

axios功能很強,支援upload/download進度處理,cancelToken機制,還支援proxy,一般只有項目級的軟體才支援proxy,
比如ionic項目環境。

底層adapter有http.js和xhr.js兩個,xhr.js沒有proxy處理代碼,決定用哪個adapter的代碼:

dispatchrequest:
var adapter = config.adapter || defaults.adapter;

如果有自訂的adapter,就執行自訂adapter,否則就執行預設的adapter,預設adapter如下決定:

function getDefaultAdapter() {
var adapter;
if (typeof XMLHttpRequest !== ‘undefined‘) {
// For browsers use XHR adapter
adapter = require(‘./adapters/xhr‘);
} else if (typeof process !== ‘undefined‘) {
// For node use HTTP adapter
adapter = require(‘./adapters/http‘);
}
return adapter;

如果是瀏覽器則執行xhr,不支援proxy,如果在nodejs運行,用http adapter,支援proxy,所以proxy是nodejs的功能。

在axios基礎之上,可以進一步封裝,比如fecth就是調用get,但做了一層封裝:
function fetch(url, params = {}) {
return new Promise((resolve, reject) => {
axios.get(url, {
params: params
})
.then(response => {
resolve(response); //因為是new執行個體時執行回調傳遞resolve,因此是resove new執行個體
})
.catch(err => {
// reject(err)
})

批量http寫法:
axios.all([getCarousel(), getthemes(), gethotline(), getArea()])
.then(axios.spread(function(getCarouselData, getthemesData, gethotlineData, getAreaData) {

裡面直接寫axios.get(url)或fetch(url)也一樣,但加一層封裝邏輯上更直觀。

 

  • axios.all()批量執行http的原始碼分析

 

axios.spread方法代碼:
function spread(callback) {
return function wrap(arr) {
return callback.apply(null, arr);
};

它是返回一個wrap封裝函數,因此實際上就是then(function wrap(arr){return callback.apply(null,arr);}),promise
會resolve之前promise執行個體再傳遞資料執行then裡面的函數,傳遞的資料是數組,函數形參寫,,,是可以的,函數代碼用
arguments[]取參數也可以,去掉spread直接寫then(funciton(){也是可以的,用arguments[0]可以擷取到數組,axios變換
參數時加了一層[0]。

關鍵還是要看all promise如何resolve,涉及到每個http promise狀態變化,挺複雜的。

axios.all代碼:
axios.all = function all(promises) {
return Promise.all(promises);
};

es6-promise:
function all(entries) {
return new Enumerator(this, entries).promise;
}

function Enumerator(Constructor, input) {
this.promise = new Constructor(noop); // all返回這個promise執行個體
this._result = new Array(this.length);
this._enumerate();
fulfill(this.promise, this._result); //在這裡resolve返回的promise執行個體並傳遞一個數組

Enumerator.prototype._enumerate = function () {
this._eachEntry(_input[i], i);

Enumerator.prototype._eachEntry = function (entry, i) {
this._settledAt(entry._state, i, entry._result);
this._willSettleAt(resolve$$(entry), i);
this._remaining--;
this._result[i] = entry;

Enumerator.prototype._settledAt = function (state, i, value) {
檢查處理一個promise,如果完成就更新all promise數組以及計數
this._remaining--;
this._result[i] = value;
if (this._remaining === 0) {
//如果所有http promise都完成則resolve axios.all返回的promise
fulfill(promise, this._result);

Enumerator.prototype._willSettleAt = function (promise, i) {
//如果http promise沒完成,就定閱一個http promise完成事件,相當於寫一個http promise.then(callback),當http promise完成時執行callback執行_settledAt更新all promise數組
subscribe(promise, undefined, function (value) {
return enumerator._settledAt(FULFILLED, i, value);

all/spread只是簡單的封裝,批處理代碼都在Enumerator這個對象中,all的底層就是Enumerator。

因此all檢查等待所有http promise完成,擷取每個http的response/value,再resovle本身promise,傳遞數組,執行then裡面
的callback,callback入口參數是數組,按all數組的書寫順序,不是按每個http完成先後順序,因為http過程是非同步,完成
順序是隨機的。等待的辦法就是用事件機制,用subscriber()方法,相當於寫了then,每個http promise原來沒寫then,all
給每個http promise寫了一個then,每個http promise完成之後更新all數組並判斷是否都完成,如果都完成就執行all後面的
then。


另外,InterceptorManager就是建一個handlers[],通過調用use可以把自訂的攔截函數儲存在裡面。

Axios底層就是調用xmlhttprequest,就是加了封裝,這樣書寫更方便,使用更方便,相當於angular的http封裝,webuploader底層也是,但webuploader是一個應用功能,
不僅僅是把http封裝一下。

 

AXIOS原始碼重點痛點分析

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.