Axios方法的定义
function Axios(instanceConfig) {
//接受默认的配置参数
this.defaults = instanceConfig;
//定义请求和响应的拦截器
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
我们在创建Axios对象时会传入一个默认的配置对象,将它赋值给内部的属性最后会和我们自己配置的参数进行合并。然后会定义请求和响应的拦截对象,用户可以手动去添加拦截函数。
Axios.prototype.request = function request(config) {
//这里就是对参数进行预处理,因为axios传入参数的方式有好几种
if (typeof config === 'string') {
config = arguments[1] || {};
config.url = arguments[0];
} else {
config = config || {};
}
//将我们配置的参数和原始参数进行合并
config = mergeConfig(this.defaults, config);
// Set config.method
if (thod) {
config.methconfig.meod = config.method.toLowerCase();
} else if (this.defaults.method) {
config.method = this.defaults.method.toLowerCase();
} else {
config.method = 'get';
}
// chain数组用来保存对config或者result处理的函数,同时这些处理函数都是成对出现的,默认传入的是dispatchRequest和undefine,dispatchRequest内部会发起真正XML的请求。
var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);
//将用户添加的请求拦截函数放入chain中,此时放入的是队头,因为它要在请求发起前执行。
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
//将用户添加的响应拦截函数放入chain中,此时放入的是队尾,因为这是要对返回的数据进行处理。
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
//循环执行,最终promise拿到的就是最终的数据
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
Axios中的request方法其实就是做了三件事:
//将['delete', 'get', 'head', 'options']方法加入到Axios的原型中
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
Axios.prototype[method] = function(url, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url
}));
};
});
//将['post', 'put', 'patch']方法加入到Axios的原型中
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
Axios.prototype[method] = function(url, data, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url,
data: data
}));
};
});
这里其实就是讲我们常用的一些请求方法加入到Axios的原型中,这样我们就行直接通过调用axios对象上的请求方法发起请求,比如axios.get('xxx', {})
,其实最终调用还是我们的Axios上的request方法。
axios对象的创建
function createInstance(defaultConfig) {
//创建axios对象,将默认配置参数传入
var context = new Axios(defaultConfig);
//通过bind方法重新创建一个函数,并向将Axios原型上的方法和属性复制给这个函数
var instance = bind(Axios.prototype.request, context);
utils.extend(instance, Axios.prototype, context);
utils.extend(instance, context);
return instance;
}
//创建请求方法,这个对象就是我们最终发送请求使用的方法,这一提醒下axios是一个函数。
var axios = createInstance(defaults);
createInstance函数的目的首先明确一下:它创建出来的是一个函数,我们可以直接调用这个函数或者原型上的请求方法发送请求
,下面详细讲解下过程:
创建一个axios对象,将我们的默认配置参数传入
调用bind函数,创建一个新的函数,函数的返回值是执行Axios.request后的返回值,其实bind实现很简单:
function bind(fn, thisArg){
return function wrap(...args){
return fn.apply(thisArg,args);
}
}
args就是我们发送请求时配置的参数,最终aegs会交给Axios.request函数,而之所以我们最后能使用axios.get()
发送请求是因为我们将Axios原型上的方法复制给了wrap函数,也就是上面的extend函数。
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
这一块就是向axios上添加取消请求的方法,让我们能够中断发送的请求,具体实现后面会介绍。
调用dispatchRequest发起请求
config.data = transformData(
config.data,
config.headers,
config.transformRequest
);
首先是将我们传入的参数进行转换,axios.defaults.transformRequest
数组中默认就有一个函数,他会根据我们参数的类型进行不同的转换,比如我们将**{“name”:“zhangsan”,“age”:12}**作为参数传入,最终结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2htYM5qg-1629534374862)(C:\Users\1\AppData\Roaming\Typora\typora-user-images\image-20210819220851881.png)]
他会被转换成JSON字符串。如果我们想自定义转换函数可以通过concat链接自定义的函数。
axios.get('xxxx', {
transformResponse: axios.defaults.transformResponse.concat(function (data, headers) {
//....
return data;
})
})
transformData
执行的时候就会遍历axios.defaults.transformRequest数组中的转换函数对数据进行转换,接下来就是发送请求。
return adapter(config).then(function onAdapterResolution(response) {
throwIfCancellationRequested(config);
//....对response进行处理
return response;
}, function onAdapterRejection(reason) {
if (!isCancel(reason)) {
throwIfCancellationRequested(config);
// Transform response data
if (reason && reason.response) {
//...对reason进行处理
}
}
return Promise.reject(reason);
});
这里最关键的其实就是调用adapter
函数发送请求,然后对返回的数据进行处理,也就是说dispatchRequest函数最终返回的是一个Promise,我们可以通过调用其then方法拿到数据。
adapter方法
首先我们来追溯下dispatchRequest中调用的adapter
是怎么拿到的。
var adapter = config.adapter || defaults.adapter;
看到这里就明白了,adapter是从我们的config中拿到的,默认情况下config上的adapter就是从默认配置中继承过来的,下面看下defaults.adapter是怎么的来的:
var defaults = {
adapter: getDefaultAdapter(),
//.....
}
function getDefaultAdapter() {
var adapter;
//根据XMLHttpRequest判断当前是不是浏览器环境,如果不是就根据process判断是不是在node环境
if (typeof XMLHttpRequest !== 'undefined') {
adapter = require('./adapters/xhr');
} else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
adapter = require('./adapters/http');
}
return adapter;
}
从getDefaultAdapter中我们就能知道axios是支持浏览器和node环境的,如果是在浏览器环境就引入XMLHttpRequest
,如果是node环境就引入http
,下面主要探究浏览器环境。
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
//...数据处理
var request = new XMLHttpRequest();
//...
request.open();
//设置超时时间
request.timeout = config.timeout;
request.onreadystatechange = function handleLoad() {
//...处理返回的数据
settle(resolve, reject, response);
request = null;
};
//监听中断操作
request.onabort = function handleAbort() {};
//监听错误
request.onerror = function handleError() {};
//监听超时
request.ontimeout = function handleTimeout() {};
if (requestData === undefined) {
requestData = null;
}
request.send(requestData);
});
};
上面就是XML请求的大致过程,从上面我们可以知道axios内部可以进行错误处理
、请求拦截
、中断请求
、请求超时处理
等功能,下面会介绍一下实现的其他功能。
withCredentials :
if (!utils.isUndefined(config.withCredentials)) {
request.withCredentials = !!config.withCredentials;
}
了解过CORS的同学应该对withCredentials不陌生,这个属性是一个布尔值,用来设置在进行跨域请求时是否携带授权信息,比如cookie
或授权header
头。
监听上传和下载进度:
//监听下载进度
if (typeof config.onDownloadProgress === 'function') {
request.addEventListener('progress', config.onDownloadProgress);
}
//监听上传进度
if (typeof config.onUploadProgress === 'function' && request.upload) {
request.upload.addEventListener('progress', config.onUploadProgress);
}
progress是XML自带的一个事件,当请求接受到更多数据时,会周期性的触发,我们在定义config的时候可以自定义上传或者下载事件,然后通过event对象可以知道上传或者下载的进度。
axios.post(action, formData, {
onUploadProgress: (e) => {
//获取当前上体积与总体积
let percentage = Math.round((e.loaded * 100) / e.total) || 0;
if(percentage < 100) {
//....进度条展示当前进度
}
}
})
中断请求:
首先看个例子,怎么去中断请求:
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('xxxxx', {
cancelToken: source.token
}).then((res) => {
console.log(res)
}).catch((err) => {
console.log(err);
});
source.cancel('中断请求');
从代码中可以知道我们首先需要给我们的请求配置上cancelToken
这个属性,然后就可以在请求返回之前随时调用cancel
方法中断请求,
下面一步一步来解析这个过程。
获取CancelToken
axios.CancelToken = require('./cancel/CancelToken');
现在知道了,axios上的CancelToken就是从./cancel/CancelToken
这个模块中导出来的。
function CancelToken(executor) {
var resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
var token = this;
executor(function cancel(message) {
if (token.reason) {
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
});
}
原来CancelToken是一个方法,这个方法就是在this
上绑定了一个promise,根据以往的经验这个this要么指向调用CancelToken的实例,要么指向new出来的新对象,暂时先放着。
接着执行executor函数,这个函数里面传递了一个cancel方法,其实这个cancel方法就是用户调用的cancel方法。
在cancel方法中我们看到它调用了promise的resolve方法将message
抛出去,看到这里可以再去看下上面的流程图。
执行CancelToken.source
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel
};
};
source方法首先帮我们new了一个新实例token,现在就知道了上面的this
指向的就是这个新实例,也就是说token上有一个pending状态的promise,等待用户去触发cancel。
这个函数返回的一个是token,这个token会作为config的CancelToken属性的值,另一个返回值就是cancel函数,也就是CancelToken中executor函数传递过来的参数。
执行cancel函数
我们现在知道执行cancel目的就是让CancelToken中的promise的状态从pendinig变为resolve状态,那这有啥用呢?因为我们生成的token最终会作为CancelToken属性的值传入,所以我们看看最终是怎么处理CancelToken属性的。
if (config.cancelToken) {
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
request.abort();
reject(cancel);
request = null;
});
}
现在应该可以恍然大悟了,在执行xml请求时会执行这段代码,如果我们配置了CancelToken就会进入判断条件里面,里面就会调用cancelToken.promise.then
,如果用户不调用cancel方法让cancelToken.promise
从pending状态变为resolve状态,那么他的then方法就不会被执行,也就不会影响正常的请求。
如果我们调用了cancel方法,那么就会执行then方法中的代码,我们看到,里面就触发了xml的abort函数,同时执行了reject
函数,我们知道axios最终的返回值就是一个promise,这里的reject
对应的就是这个promise。
总结
axios是一个非常强大的库,一般日常开发都会使用,同时里面的一点调用逻辑也是值得去学习的,比如说最后的cancel逻辑,他将axios返回的promise控制权交给用户,当用户想终止可以直接触发他的reject方法。