概述
我们引入的 axios 实际上是Axios的原型方法 request (函数对象),该函数的执行上下文是 Axios 的一个实例对象。
- 该实例扩展了
Axios构造函数原型上的方法,以及公有属性defaults、interceptors。 - 暴露了
axios.Axios以允许其他的类进行继承。 - 提供了
create方法用于创建新实例的工厂函数。 - 提供了
Cancel、CancelToken、isCancel用于中断请求。 - 提供了
all、spread方法用于同时发送多个请求。
createInstance()
// lib/axios.js
/**
* 创建一个 Axios 实例
* @param {Object} defaultConfig 实例的默认配置
* @return {Axios} 返回一个新的Axios实例
*/
function createInstance(defaultConfig){
// 创建一个axios 实例 主要是当作 axios 执行的上下文,下面的扩展的方法都会将该实例绑定为 this
// 为什么要这么做呢?
// 假如不这样做,只是创建一个实例,通过原型方法实现有什么区别?
var context = new Axios(defaultConfig)
// 将 request 方法作为 instance 实例导出为 axios (实际是request方法)
var instance = bind(Axios.prototype.request, context)
// 将Axios原型上的方法扩展到实例上 并绑定函数的 this 为 上面创建的context实例。
Utils.extend(instance, Axios.prototype, context)
// 将 Axios 构造函数的公有属性扩展到该实例上
Utils.extend(instance, context)
return instance
}
var axios = createInstance(defaults);
module.exports = axios;
// Allow use of default import syntax in TypeScript
module.exports.default = axios;
// lib/helpers/bind.js
function bind(fn, thisArg){
return function warp(){
var args = new Array(arguments.length);
for(var i = 0; i < args.length; i++){
args[i] = arguments[i]
}
return fn.apply(thisArg, args)
}
}
// lib/utils.js
/**
* Extends object a by mutably adding to it the properties of object b.
*
* @param {Object} a The object to be extended
* @param {Object} b The object to copy properties from
* @param {Object} thisArg The object to bind function to
* @return {Object} The resulting value of object a
*/
function extend(a, b, thisArg){
forEach(b, function assignValue(val, key){
if(thisArg && typeof val === 'function'){
a[key] = bind(val, thisArg);
}else{
a[key] = val
}
})
}
/**
* Iterate over an Array or an Object invoking a function for each item.
*
* If `obj` is an Array callback will be called passing
* the value, index, and complete array for each item.
*
* If 'obj' is an Object callback will be called passing
* the value, key, and complete object for each property.
*
* @param {Object|Array} obj The object to iterate
* @param {Function} fn The callback to invoke for each item
*/
function forEach(obj, fn){
if(obj === null && typeof obj === 'undefined'){
return
}
if(typeof obj !== 'object'){
obj = [obj]
}
if(isArray(obj)){
for(var i = 0, l = obj.length; i < l; i++){
fn.call(null, obj[i], i, obj)
}
}else{
for(var key in obj){
if(Object.prototype.hasOwnProperty.call(obj, key)){
fn.call(null, obj[key], key, obj)
}
}
}
}
Axios构造函数
公有属性
defaults用于保存Axios 库的默认配置项interceptors包含request和response两个请求拦截器。
原型方法
request:用于发送请求的主要方法。getUri:获取请求Uri。delete、get、head、options:请求方法的别名,实际上调用的还是request方法。post、put、patch:请求方法的别名,实际上调用的还是request方法。
// lib/core/Axios.js
function Axios(instanceConfig){
this.defaults = instanceConfig;
this.insterceptors = {
requset: new InterceptorManager(),
reqponse: new InterceptorManager()
}
}
Axios.prototype.request = function request(config){
// 可以使用 axios(config) 或者 axios(url[, config])的调用方式
// 获取请求的url
if(typeof config === 'string'){
config = arguments[1] || {};
config.url = arguments[0];
}else{
config = config || {}
}
// 合并请求配置信息
config = mergeConfig(this.defaults, config)
// 获取请求方法 默认为get
if(config.method){
config.method = config.method.toLowerCase()
}else if{this.defaults.method}{
config.method = this.defaults.method.toLowerCase()
}else{
config.method = 'get'
}
// 连接拦截器中间件 chain 用于注册promise的回调链
var chain = [dispatchRequest, undefined];
// 生成一个成功的promise实例,并将请求配置信息传入
var promise = Promise.resolve(config);
// 将请求拦截器放入 回调链的最前面
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor){
chain.unshift(interceptor.fulfilled, interceptor.rejected)
})
// 将响应拦截器放入 回调链的最后面
this.interceptors.response.forEach(function pushRequestInterceptors(interceptors){
chain.push(interceptor.fulfilled, interceptor.rejected)
})
// 依次将回调函数绑定到promise回调链上 如果有报错,根据promise的异常传透原理,会将异常依次抛到下一层
// promise.then(请求拦截器).then(发送请求).then(响应拦截器)
while(chain.length){
promise = promise.then(chain.shift(), chain.shift())
}
return promise
}
//
Axios.prototype.getUri = function getUri(config){
config = mergeConfig(this.defaults, config)
return buildURL(congig.url, config.params, config.paramsSerializer).replace(/^\?/,'')
}
// 原型上添加不含data的请求方法别名
utils.forEach(['get','head','options','delete'], function forEachMethodNoData(method){
Axios.prototype[method] = function(url, config){
return this.request(mergeConfig(config || {}, {
method: method,
url: url
}))
}
})
// 原型上添加含data的请求方法别名
utils.forEach(['post', 'put', 'petch'], function forEachMethodWithData(method){
Axios.prototype[method] = function(url, data, config){
return this.request(mergeConfig(config || {}, {
method,
url,
data
}))
}
})
module.export = Axios
拦截器
Axios为我们提供了请求拦截器和响应拦截器。注册的拦截器会存在 公有属性 interceptors 的request 和 response属性上。通过一个 interceptorManager实例管理。
下面我们来看下 InterceptorManager 构造函数是怎么实现的。
- 通过调用
use方法向栈中存入拦截器 - 通过调用
eject方法移除栈中的拦截器 - 通过调用
forEach方法遍历栈中的拦截器,并注册拦截器。
function InterceptorManager(){
this.handlers = []
}
/**
* Add a new interceptor to the stack
* 向handlers中添加一个新的拦截器
* @param {Function} fulfilled The function to handle `then` for a `Promise`
* @param {Function} rejected The function to handle `reject` for a `Promise`
*
* @return {Number} An ID used to remove interceptor later
* 返回拦截器的ID 在移除的时候使用
*/
InterceptorManager.prototype.use = function use(fulfilled, rejected){
this.handlers.push({
fulfilled,
rejected
})
return this.handler.length - 1
}
/**
* Remove an interceptor from the stack
*
* @param {Number} id The ID that was returned by `use`
*/
InterceptorManager.prototype.eject = function eject(id){
if(this.handlers[id]){
this.handlers[id] = null
}
}
/**
* Iterate over all the registered interceptors
* 遍历handlers,注册拦截器
* This method is particularly useful for skipping over any
* interceptors that may have become `null` calling `eject`.
*
* @param {Function} fn The function to call for each interceptor
*/
InterceptorManager.prototype.forEach = function forEach(fn){
utils.forEach(this.handlers, function forEachHandler(h){
if(h !== null){
fn(h)
}
})
}
dispatchRequest()
该方法是 request() 中用于向服务器发送请求的主要方法。其返回结果也是一个 promise。
function transformData(data, headers, fns){
utils.forEach(fns, function(fn){
data = fn(data, headers)
})
return data
}
function dispatchRequest(config){
// 用于让用户手动取消请求
throwIfCancellationRequested(config)
// 确保请求头存在
config.headers = config.headers || {};
// 请求数据转换
// 将 data 与 headers 传入指定的函数进行数据转换 config.transformRequest 是一个数组
// 里面包含了默认的请求数据转换方法 以及用户自定义添加的(如果有的话)
config.data = transformData(
config.data,
config.headers,
config.transformRequest
)
// 请求头摊平
confif.headers = utils.merge(
config.headers.common || {},
config.headers[config.method] || {},
config.headers
)
utils.forEach(
['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
function clearHeaderConfig(){
delete config.headers[method]
}
)
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)){
throwIfCancellationRequestd(config)
// 转换响应数据
reason.response = transformData(
reason.response.data,
reason.response.headers,
config.transformResponse
)
}
return Promise.reject(reason)
}
)
}
adapter 请求适配器
axios不仅适用于浏览器还适用于 Node.js。所以在请求的方法上也实现了两种方案。
- 浏览器使用
XMLHttpRequest - Node.js使用
http或https中间件
function getDefaultAdapter() {
var adapter;
if (typeof XMLHttpRequest !== 'undefined') {
// For browsers use XHR adapter
adapter = require('./adapters/xhr');
} else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
// For node use HTTP adapter
adapter = require('./adapters/http');
}
return adapter;
}
我们来看一下 XMLHttpRequest 是如何处理的。
// lib/adapters/xhr
module.exports = function xhrAdapter(config){
return new Promise(function dispatchXhrRequest(resolve, reject){
var requestData = config.data;
var requestHeaders = config.headers;
// 如果是FormData
if(utils.isFormData(requestData)){
delete requestHeaders['Content-Type'] // 让浏览器自己设置
}
if(utils.isBlob(requestData) || utils.isFile(requestData) && requestData.type){
delete requestHeaders['Content-Type'] // 让浏览器自己设置
}
// 创建异步请求
var request = new XMLHttpRequest();
// HTTP基本身份验证
if(config.auth){
var username = config.auth.username || '';
var password = unescape(encodeURIComponent(config.auth.password)) || '';
requestHeaders.Authorization = 'Basic' + btoa(username + ':' + password);
}
// 拼接完整的请求地址,如果是绝对路径,直接返回。
var fullPath = buildFullPath(config.baseURL, config.url)
request.open(config.method.toUpperCase(),
buildURL(fullPath, config.params, config.paramsSerializer), true );
// Set the request timeout in MS
request.timeout = config.timeout;
request.onreadystatechange = function handleLoad(){
// 0 代理被创建,但还没调用 open()
// 1 open() 已被调用
// 2 send() 已被调用,并且头部和状态可获得
// 3 下载中 responseText 属性已经包含部分数据
// 4 下载完成
// 请求状态码 为4 代表请求已完成 下载操作已完成。
if(!request || request.readyState !== 4){
return;
}
// 如果请求出错了,我们没有得到响应,将会由 onerror 处理
// 有一个例外情况,如果请求使用的 file: 协议,大多是浏览器会返回 0 的状态码,尽管请求成功了。
if(
request.status === 0 &&
!(request.responseURL && request.responseURL.indexof('file:') ===0)
){
return;
}
// 准备响应数据
// 获取响应头
var responseHeaders = 'getAllResponseHeaders' in request ? getAllResponseHeaders() : null
// 如果响应的类型是 text 则取responseText作为结果值
var responseData = !config.responseType || config.reponseType === 'text' ?
request.responseText : request.response;
var response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config: config,
request: request
}
// 根据响应码来判断 成功 or 失败 用户可以自定义 vaildateStatus
settle(resolve, reject, response)
// 清除request 请求
request = null
}
// 处理浏览器取消请求(不是手动取消)
request.onabort = function handleAbort(){
if (!request) {
return;
}
reject(createError('Request aborted', config, 'ECONNABORTED', request));
// Clean up request
request = null;
}
// 处理网速慢时的网络错误
request.onerror = function handleError(){
// Real errors are hidden from us by the browser
// onerror should only fire if it's a network error
reject(createError('Network Error', config, null, request));
// Clean up request
request = null;
}
// Handle timeout
request.ontimeout = function handleTimeout() {
var timeoutErrorMessage = 'timeout of ' + config.timeout + 'ms exceeded';
if (config.timeoutErrorMessage) {
timeoutErrorMessage = config.timeoutErrorMessage;
}
reject(createError(timeoutErrorMessage, config, 'ECONNABORTED',
request));
// Clean up request
request = null;
};
// 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()) {
// Add xsrf header
var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath))
&& config.xsrfCookieName
? cookies.read(config.xsrfCookieName)
: undefined;
if (xsrfValue) {
requestHeaders[config.xsrfHeaderName] = xsrfValue;
}
}
// Add headers to the request
if ('setRequestHeader' in request) {
utils.forEach(requestHeaders, function setRequestHeader(val, key) {
if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') {
// Remove Content-Type if data is undefined
delete requestHeaders[key];
} else {
// Otherwise add header to the request
request.setRequestHeader(key, val);
}
});
}
// Add withCredentials to request if needed
if (!utils.isUndefined(config.withCredentials)) {
request.withCredentials = !!config.withCredentials;
}
// Add responseType to request if needed
if (config.responseType) {
try {
request.responseType = config.responseType;
} catch (e) {
// Expected DOMException thrown by browsers not compatible XMLHttpRequest Level 2.
// But, this can be suppressed for 'json' type as it can be parsed by default 'transformResponse' function.
if (config.responseType !== 'json') {
throw e;
}
}
}
// Handle progress if needed
if (typeof config.onDownloadProgress === 'function') {
request.addEventListener('progress', config.onDownloadProgress);
}
// Not all browsers support upload events
if (typeof config.onUploadProgress === 'function' && request.upload) {
request.upload.addEventListener('progress', config.onUploadProgress);
}
if(config.cancelToken){
// 处理取消事件
config.cancelToken.promise.then(function onCanceled(cancel){
if(!request){
return
}
request.abort();
reject(cancel);
request = null
})
}
if (!requestData) {
requestData = null;
}
// Send the request
request.send(requestData);
})
}
我们来看一下 自定义状态码成功与失败的方法处理
function settle(resolve, reject, response){
var validateStatus = response.config.validateStatus;
if(!response.status || !validateStatus || validataStatus(response.status)){
resolve(response)
}else{
reject(createError(
'Request failed with status code ' + response.status,
response.config,
null,
response.request,
response
)
)
}
}
// 默认的validateStatus为
function validateStatus(status){
return status >= 200 && status < 300
}