簡介
axios 是一個用于瀏覽器和Node.js上的基于 Promise 的http網(wǎng)絡庫点楼。
大綱
使用方式
安裝:
npm install axios
使用:
//引入axios
const axios = require('axios');
import axios from 'axios';
axios的四種使用方式
1. axios(config)
直接將相關配置包括請求url作為參數(shù)傳入到axios方法中
axios({
url: 'https://jsonplaceholder.typicode.com/todos/1',
method: 'get'
}).then(response => {
console.log(response.data)
}).catch(error => {
console.log(error)
});
2. axios(url[, config])
還是使用axios方法印衔,但是第一個參數(shù)傳入請求url担猛,第二個參數(shù)傳入其他配置參數(shù)。
axios('https://jsonplaceholder.typicode.com/todos/1', {
method: 'get'
}).then(response => {
console.log(response.data)
}).catch(error => {
console.log(error)
});
3. axios[method](url[, config])
使用axios暴露出來的get,post,delete,put等請求方法湃崩,參數(shù)設置同第2種 axios(url[, config])
axios.get('https://jsonplaceholder.typicode.com/todos/1', {
timeout: 1000
}).then(response => {
console.log(response.data)
}).catch(error => {
console.log(error);
});
4. axios.request(config)
使用axios暴露出來的request方法承耿,參數(shù)設置同第1種axios(config)
axios.request({
url: 'https://jsonplaceholder.typicode.com/todos/1',
timeout: 1000
}).then(response => {
console.log(response.data)
}).catch(error => {
console.log(error)
});
請求配置
在上一步的發(fā)起請求的方法中,我們都能看到config這個配置參數(shù)各墨,通過設置這個參數(shù)的值指孤,可以達到配置請求的目的。在axios中贬堵,config是溝通調用方和網(wǎng)絡庫的橋梁恃轩,
常用的配置項如下所示:
{
// `url` 是用于請求的服務器 URL,相對路徑/絕對路徑
url: '/api/users',
// `method` 是創(chuàng)建請求時使用的http方法黎做,包括get, post, put, delete等
method: 'get', // default
// `baseURL` 將自動加在 `url` 前面叉跛,除非 `url` 是一個絕對 URL。
// 它可以通過設置一個 `baseURL` 便于為 axios 實例的方法傳遞相對 URL
baseURL: 'https://some-domain.com/api/',
// `transformRequest` 允許在向服務器發(fā)送前蒸殿,修改請求數(shù)據(jù)
// 只能用在 'PUT', 'POST' 和 'PATCH' 這幾個請求方法
// 后面數(shù)組中的函數(shù)必須返回一個字符串筷厘,或 ArrayBuffer,或 Stream
transformRequest: [function (data, headers) {
// 對 data 進行任意轉換處理
return data;
}],
// `transformResponse` 在傳遞給 then/catch 前宏所,允許修改響應數(shù)據(jù)
transformResponse: [function (data) {
// 對 data 進行任意轉換處理
return data;
}],
// `headers` 是即將被發(fā)送的自定義請求頭
headers: {'X-Requested-With': 'XMLHttpRequest'},
// `params` 是即將與請求一起發(fā)送的 URL 參數(shù)
// 必須是一個無格式對象(plain object)或 URLSearchParams 對象
params: {
name: 'John'
},
// `paramsSerializer` 是一個負責 `params` 序列化的函數(shù)
// (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
paramsSerializer: function(params) {
return Qs.stringify(params, {arrayFormat: 'brackets'})
},
// `data` 是作為請求主體被發(fā)送的數(shù)據(jù)
// 只適用于這些請求方法 'PUT', 'POST', 和 'PATCH'
// 在沒有設置 `transformRequest` 時酥艳,必須是以下類型之一:
// - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
// - 瀏覽器專屬:FormData, File, Blob
// - Node 專屬: Stream
data: {
firstName: 'John'
},
// `timeout` 指定請求超時的毫秒數(shù)(0 表示無超時時間)
// 如果請求花費了超過 `timeout` 的時間,請求將被中斷
timeout: 1000,
// `adapter` 允許自定義處理請求爬骤,以使測試更輕松
// 返回一個 promise 并應用一個有效的響應 (查閱 [response docs](#response-api)).
adapter: function (config) {
/* ... */
},
// `auth` 表示應該使用 HTTP 基礎驗證充石,并提供憑據(jù)
// 這將設置一個 `Authorization` 頭,覆寫掉現(xiàn)有的任意使用 `headers` 設置的自定義 `Authorization`頭
auth: {
username: 'janedoe',
password: 's00pers3cret'
},
// `responseType` 表示服務器響應的數(shù)據(jù)類型霞玄,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
responseType: 'json', // default
// `responseEncoding` 表示用于響應數(shù)據(jù)的解碼方式
responseEncoding: 'utf8', // default
// `validateStatus` 定義對于給定的HTTP 響應狀態(tài)碼是 resolve 或 reject promise 骤铃。如果 `validateStatus` 返回 `true` (或者設置為 `null` 或 `undefined`),promise 將被 resolve; 否則坷剧,promise 將被 rejecte
validateStatus: function (status) {
return status >= 200 && status < 300; // default
},
// `cancelToken` 指定用于取消請求的 cancel token
cancelToken: new CancelToken(function (cancel) {
}),
...
}
攔截器
攔截器惰爬,類似于中間件的概念,是axios的核心功能之一惫企,主要是分為兩種:請求攔截器和響應攔截器撕瞧。有了攔截器,我們能在網(wǎng)絡請求之前,對網(wǎng)絡請求配置做處理风范。在返回數(shù)據(jù)之前咨跌,對返回數(shù)據(jù)做處理沪么。
中間件硼婿,攔截器: 一般用于對一個目標方法的前置或后置切片操作,可以將一些額外的臟邏輯寫到其他的文件中管理禽车,提高目標方法的簡潔性寇漫。
使用方式:
//請求攔截器
const requestInterceptor = axios.default.interceptors.request.use((config) => {
//在請求發(fā)送前,對請求配置(AxiosRequestConfig)做一些處理
return config;
}, (error) => {
return Promise.reject(error);
});
//移除之前添加的攔截器
axios.default.interceptors.request.eject(requestInterceptor);
//響應攔截器
axios.default.interceptors.response.use((response) => {
//對請求響應數(shù)據(jù)做一些處理
return response;
}, (error) => {
return Promise.reject(error);
});
取消請求
支持取消請求也是axios的一個核心功能殉摔,在配置中實現(xiàn)一個cancelToken的參數(shù)就能取消州胳。
//取消請求
const cancelToken = axios.CancelToken;
const source = cancelToken.source();
axios.get('https://jsonplaceholder.typicode.com/todos/1', {
cancelToken: source.token
}).then(response => {
console.log(response.data);
}).catch(error => {
if(axios.isCancel(error)) {
console.log(error.message);
} else {
console.log(error)
}
});
source.cancel('canceled by user');
默認配置
請求配置可以在每個請求中單獨設置,也可以在defaults中為全局設置逸月。
//默認baseUrl
axios.defaults.baseUrl = 'https://jsonplaceholder.typicode.com';
//默認超時時間
axios.defaults.timeout = 3000;
//默認Authorization頭
axios.defaults.headers.common['Authorization'] = 'AUTH_TOKEN';
數(shù)據(jù)轉換
請求數(shù)據(jù)轉換
axios.defaults.transformRequest.push((data, headers)=>{
//處理請求的data
return data;
});
返回數(shù)據(jù)轉換
axios.defaults.transformResponse.push((data, headers)=>{
//處理返回的data
return data;
});
源碼解析
源碼分析基于0.19.2版本
首先看下源碼的目錄結構:
請求分析
首先從axios的幾種請求方式來入手栓撞,我們從axios庫中導入的axios對象。找到源碼axios.js類碗硬,可以看到創(chuàng)建的默認axios對象瓤湘。
//axios.js
function createInstance(defaultConfig) {
var context = new Axios(defaultConfig);//創(chuàng)建Axios實例
//將context綁定到Axios的request方法上
//也可以這樣實現(xiàn):var instance = Axios.prototype.request.bind(context);
//instance指向了request方法,并且上下文是實例context
//所以我們能直接以axios(url, {config})的方式來發(fā)送請求恩尾。本質上還是調用的request方法
var instance = bind(Axios.prototype.request, context);
// Copy axios.prototype to instance
//把Axios.prototype上的方法拓展到instance上弛说,同時上下文是context,也就是this指向context
//所以我們能以axios.get/post的方式發(fā)送請求
utils.extend(instance, Axios.prototype, context);
//將context上的屬性和方法拓展到instance上
//所以我們以axios.defaults,axios.interceptors能獲取到攔截器和默認屬性
// Copy context to instance
utils.extend(instance, context);
return instance;
}
// Create the default instance to be exported
var axios = createInstance(defaults);
module.exports = axios;
從以上源碼可以得知翰意,axios中導出的axios對象是通過createInstance方法以及默認配置defaults來創(chuàng)建的木人。
createInstance方法沒有僅僅創(chuàng)建Axios實例,還做了一系列綁定和拓展的操作冀偶,使得獲得的Axios實例支持axios(url,{config})和axios.get/post
這種請求方式醒第。
Axios類及request方法分析
從前面的分析可知,不管是創(chuàng)建的默認實例還是用自定義配置創(chuàng)建的實例进鸠,以及axios請求的幾種寫法稠曼,都和Axios類以及request方法息息相關。
function Axios(instanceConfig) {
this.defaults = instanceConfig; //默認配置
this.interceptors = { //攔截器
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
Axios.prototype.request = function request(config) {
if (typeof config === 'string') {//為了支持axios(url, {config})這種寫法
config = arguments[1] || {};
config.url = arguments[0];
} else {
config = config || {};
}
config = mergeConfig(this.defaults, config);//合并配置
//設置請求方法
if (config.method) {
config.method = config.method.toLowerCase();
} else if (this.defaults.method) {
config.method = this.defaults.method.toLowerCase();
} else {
config.method = 'get';
}
//通過以promise resolve, reject為一組值的鏈式數(shù)組堤如,來支持攔截器中間件蒲列,并將配置參數(shù)傳給dispatchRequest方法
// config配置--> 請求攔截器 --> dispatchRequest--> 響應攔截器
var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);//請求攔截器插入到數(shù)組前部
});
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected); //響應攔截器插入到數(shù)組尾部
});
while (chain.length) {//遍歷生成最終的請求promise(包含配置信息,請求攔截器搀罢,dispatchRequest方法蝗岖,響應攔截器)
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
//為了支持axios.get(url, config)這種寫法
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url
}));
};
});
//為了支持axios.post(url, data, config)這種寫法
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, data, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url,
data: data
}));
};
});
通過以上源碼,Axios類有默認配置和攔截器兩個屬性值榔至,同時類上有Axios的核心方法request抵赢。
- request方法只聲明了一個參數(shù)config,但是通過判斷config的類型是否是字符串,巧妙的支持了axios(url,config)這種寫法铅鲤。
- config = mergeConfig(this.defaults,config);合并了默認配置和請求上設置的配置划提。結合axios類中的create工廠方法的代碼可以知道,配置信息的優(yōu)先級由高到低分別是請求方法上的>創(chuàng)建axios實例的> axios默認的
- axios支持promise是通過在request方法中按照Promise中的then方法中的參數(shù)結構邢享,一個resolve和一個reject為一組將dispatchRequest鹏往,請求攔截器和響應攔截器塞進數(shù)組中的。
// axios內(nèi)部Promise的簡要流程
Promise.resolve(config).then(function requestInterceptorFulfill(config) {
return config;
}, function requestInterceptorReject(error) {
return Promise.reject(error);
}).then(function dispatchrequest(config) {
return dispatchRequest(config);
}, undefined).then(function responseInterceptorFulfill(response) {
return response;
}, function responseInterceptorReject(error) {
return Promise.reject(error);
});
dispatchRequest分析
通過上面的源碼骇塘,我們知道了axios是如何支持攔截器的伊履,以及config在內(nèi)部的流動方向。其中,有個dispatchRequest方法款违,還沒有分析它做了什么唐瀑。
從字面意思來看,dispatchRequest 就是發(fā)送請求的意思插爹,查看源碼哄辣,可以發(fā)現(xiàn)這個方法主要做了這幾件事情:
1.支持取消請求
2.對請求數(shù)據(jù)做轉換
3.處理請求頭
4.使用網(wǎng)絡請求適配器adapter以及配置config發(fā)送請求
5.對返回數(shù)據(jù)做轉換
module.exports = function dispatchRequest(config) {
//如果設置了cancelToken則直接取消請求,后續(xù)會分析取消請求的相關源碼
throwIfCancellationRequested(config);
// 確保headers存在
config.headers = config.headers || {};
// 對請求的數(shù)據(jù)做轉換
config.data = transformData(
config.data,
config.headers,
config.transformRequest
);
// 合并headers
config.headers = utils.merge(
config.headers.common || {},
config.headers[config.method] || {},
config.headers
);
utils.forEach(
['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
function cleanHeaderConfig(method) {
delete config.headers[method];
}
);
// 獲取config上設置的網(wǎng)絡請求適配器(若沒有則使用默認的)
// axios中有兩個預定義的適配器:分別是nodejs中的http和瀏覽器中的XMLHttpRequest
var adapter = config.adapter || defaults.adapter;
//將配置config傳入adpater中赠尾,return這個promise
return adapter(config).then(function onAdapterResolution(response) {
//如果設置了cancelToken則直接取消請求
throwIfCancellationRequested(config);
// 對返回的數(shù)據(jù)做轉換
response.data = transformData(
response.data,
response.headers,
config.transformResponse
);
return response;
}, function onAdapterRejection(reason) {
if (!isCancel(reason)) {
throwIfCancellationRequested(config);
// 對返回的數(shù)據(jù)做轉換
if (reason && reason.response) {
reason.response.data = transformData(
reason.response.data,
reason.response.headers,
config.transformResponse
);
}
}
return Promise.reject(reason);
});
};
可以看到力穗,就算是走到了dispatchRequest方法內(nèi)部,也不是真正發(fā)送請求的地方萍虽。源碼告訴我們睛廊,請求是從adapter內(nèi)部發(fā)送出去的。
adapter-xhr分析
在axios內(nèi)部杉编,默認定義了兩種請求適配器超全,分別是nodejs端的http和瀏覽器端的xhr。在這里主要分析xhr的源碼邓馒。
xhr:
即XMLHttpRequest,具體用法可以參考MDN文檔https://developer.mozilla.org/en/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest
//xhr的精簡源碼嘶朱,刪除了一些非重點代碼
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
//構造一個XMLHttpRequest對象
var request = new XMLHttpRequest();
//構造請求完整路徑(相對路徑->絕對路徑)
var fullPath = buildFullPath(config.baseURL, config.url);
//根據(jù)配置config中的數(shù)據(jù),初始化請求
//open方法三個參數(shù)分別為:請求方法,url,是否異步
//https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/open
request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);
//設置監(jiān)聽請求的onreadystatechange回調事件
request.onreadystatechange = function handleLoad() {
//響應頭
var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;
//響應數(shù)據(jù)
var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response;
//構造axios中的響應對象
var response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config: config,
request: request
};
//根據(jù)響應的狀態(tài)光酣,返回promise的reslove或reject
settle(resolve, reject, response);
request = null;
};
//設置監(jiān)聽請求的onabort回調事件
request.onabort = function handleAbort() {
reject(createError('Request aborted', config, 'ECONNABORTED', request));
request = null;
};
//設置監(jiān)聽請求的onerror回調事件
request.onerror = function handleError() {
reject(createError('Network Error', config, null, request));
request = null;
};
//設置監(jiān)聽請求的ontimeout回調事件
request.ontimeout = function handleTimeout() {
var timeoutErrorMessage = 'timeout of ' + config.timeout + 'ms exceeded';
if (config.timeoutErrorMessage) {
timeoutErrorMessage = config.timeoutErrorMessage;
}
reject(createError(timeoutErrorMessage, config, 'ECONNABORTED',
request));
request = null;
};
//若設置了cancelToken,則取消請求
if (config.cancelToken) {
config.cancelToken.promise.then(function onCanceled(cancel) {
request.abort();//中斷請求
reject(cancel);//使用cancel信息返回promise的reject
request = null;
});
}
if (requestData === undefined) {
requestData = null;
}
request.send(requestData);//使用請求數(shù)據(jù)requestData,最終發(fā)送請求
});
};
可以看到疏遏,adapter中封裝了使用XMLHttpRequest的具體細節(jié),包括救军,創(chuàng)建XHR對象财异,初始化請求,構造請求鏈接唱遭,設置請求參數(shù)戳寸,構造響應對象等等
取消請求分析
在前面,我們講到了兩種取消請求的用法拷泽,現(xiàn)在就分析下取消請求相關部分的源碼疫鹊。
//CancelToken.js
function CancelToken(executor) {
var resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
var token = this;
executor(function cancel(message) {
if (token.reason) {//已經(jīng)取消過了
return;
}
//構造Cancel類袖瞻,用于標志是否是取消請求,同時設置取消請求的信息
token.reason = new Cancel(message);
resolvePromise(token.reason);
});
//xhr.js
if (config.cancelToken) {
// 處理取消請求的情況
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
request.abort();//中斷請求
reject(cancel);
request = null;
});
}
通過以上源碼拆吆,我們知道
1.CancelToken內(nèi)部聲明了promise成員變量this.promise = new Promise(function promiseExecutor(resolve) { resolvePromise = resolve; });聋迎。
2.構造CancelToken對象的時候,傳入的executor方法在其中執(zhí)行枣耀,并傳入了一個cancel方法作為參數(shù)霉晕,在這個cancel方法中,判斷了這個請求是否已經(jīng)取消過奕枢,構造了Cancel類娄昆,用于存儲取消信息,然后將cancel對象通過保存的promise的reslove方法傳出去缝彬。
3.在xhr代碼中,第二步resolve的cancel對象哺眯,通過then方法繼續(xù)傳遞谷浅,并在其中中斷了請求,并通過xhr的promise的reject方法傳到外部奶卓。也就是我們使用axios請求的catch中得到的一疯。
4.在使用CancelToken的時候,會把第2步中的cancel方法保存下來夺姑,當需要取消請求的時候再像這樣調用墩邀。cancel('Cancel by user!')。方法參數(shù)就是Cancel對象中的message盏浙。
梳理一下:
//xhr中的promise
new Promise((resolve, reject)=>{
let request = {
abort: ()=>{
}
};
//CancelToken中的promise
Promise.resolve(new Cancel('Cancel by User!')).then(cancel => {
request.abort();//中斷請求
reject(cancel);//將cancel對象reject出去
});
}).catch(error => {
if(axios.isCancel(error)) { //捕獲在xhr中reject出來的cancel對象并打印message
console.log(cancel.message);
}
});
參考:【
https://blog.csdn.net/qq_27053493/article/details/97462300
https://www.cnblogs.com/JohnTsai/p/axios.html
https://zhuanlan.zhihu.com/p/156862881
https://zhuanlan.zhihu.com/p/33918784
】