Axios原理
Axios的使用方式
import axios form 'axios'
// 第一种 axios(config)
axios({
method: 'post',
url: '/user/12345',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
});
// 第二种 axios(url[,config])
axios('/user/12345');
// 第三种 axios.[methods]
axios.request(config)
axios.get(url[,config])
// 第四种 创建axios实例
const instance = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});
相关实现
/**
* Create an instance of Axios
*
* @param {Object} defaultConfig The default config for the instance
*
* @returns {Axios} A new instance of Axios
*/
function createInstance(defaultConfig) {
const context = new Axios(defaultConfig); //初始化axios实例
// 绑定context的this为Axios.request
// 实现axios(config)
const instance = bind(Axios.prototype.request, context);
// 给instance(绑定Axios.request的axios实例)挂载Axios.prototype
// 实现axios.get() axios.post()等方法调用
utils.extend(instance, Axios.prototype, context, {allOwnKeys: true});
// 给instance绑定context上的方法
utils.extend(instance, context, {allOwnKeys: true});
// 添加create方法 使得用户可以自己创建axios实例
instance.create = function create(instanceConfig) {
return createInstance(mergeConfig(defaultConfig, instanceConfig));
};
return instance;
}
// 创建axios实例导出
const axios = createInstance(defaults);
当我们调用axios()的方法的时候,事实上是调用了Axios.prototype.request方法,create创造的实例最终也是调用了Axios.prototype.request方法
// Axios.prototype.request
class Axios {
request(configOrUrl, config){
// 判断configOrUrl类型,合并配置
if (typeof configOrUrl === 'string') {
// 对应 axios(url,{})形式
config = config || {};
config.url = configOrUrl;
} else {
// 对应axios({})这种形式
config = configOrUrl || {};
}
// 合并配置
config = mergeConfig(this.defaults, config);
// ...
}
}
// 为Axios设置请求方法 使得满足axios.get axios.post等使用方式 最终都是调用 axios.request
utils.forEach(['delete', 'get', 'head', 'options'],
function forEachMethodNoData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, config) {
return this.request(mergeConfig(config || {}, {
method,
url,
data: (config || {}).data
}));
};
});
utils.forEach(['post', 'put', 'patch'],
function forEachMethodWithData(method) {
/*eslint func-names:0*/
function generateHTTPMethod(isForm) {
return function httpMethod(url, data, config) {
return this.request(mergeConfig(config || {}, {
method,
headers: isForm ? {
'Content-Type': 'multipart/form-data'
} : {},
url,
data
}));
};
}
Axios.prototype[method] = generateHTTPMethod();
Axios.prototype[method + 'Form'] = generateHTTPMethod(true);
});
关于axios的多种使用方式通过上述源码逻辑,最终可以发现都会调用Axios.prototype.request方法。那么可以推断出我们日常写的请求和相应拦截器也是在request里面进行处理的。
// 注册拦截器
// axios.interceptors.request.use()
class Axios {
constructor(instanceConfig) {
this.defaults = instanceConfig;
// 注册拦截器实现
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
class InterceptorManager {
constructor() {
this.handlers = [];
}
use(fulfilled, rejected, options) {
this.handlers.push({
fulfilled,
rejected,
synchronous: options ? options.synchronous : false,
runWhen: options ? options.runWhen : null
});
return this.handlers.length - 1;
}
// ...
}
// 执行请求和拦截器并且保证拦截器的执行顺序
class Axios {
request(){
// ...
// 转换请求拦截器
const requestInterceptorChain = [];
let synchronousRequestInterceptors = true;
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
return;
}
synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// 转换响应拦截器
const responseInterceptorChain = [];
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
let promise; // 处理请求的promise
let i = 0;
let len;
if (!synchronousRequestInterceptors) {
// 整合请求拦截器 请求处理 响应拦截器 形成一个数组
const chain = [dispatchRequest.bind(this), undefined];
chain.unshift.apply(chain, requestInterceptorChain);
chain.push.apply(chain, responseInterceptorChain);
len = chain.length;
promise = Promise.resolve(config);
// 循环chain
while (i < len) {
promise = promise.then(chain[i++], chain[i++]);
}
return promise;
}
}
axios整个拦截器相关的处理都在Axios.prototype.request函数里面处理了 ,真正发送请求的处理是在dispatchRequest这个函数中处理的。
export default function dispatchRequest(config) {
// 转换请求数据
config.data = transformData.call(
config,
config.transformRequest
);
// 请求适配器 区分浏览器和node环境(通过是否存在xhr)
const adapter = config.adapter || defaults.adapter;
return adapter(config).then(function onAdapterResolution(response) {
throwIfCancellationRequested(config);
// 转换响应数据
response.data = transformData.call(
config,
config.transformResponse,
response
);
response.headers = AxiosHeaders.from(response.headers);
return response;
}, function onAdapterRejection(reason) {
if (!isCancel(reason)) {
throwIfCancellationRequested(config);
// 转换响应数据
if (reason && reason.response) {
reason.response.data = transformData.call(
config,
config.transformResponse,
reason.response
);
reason.response.headers = AxiosHeaders.from(reason.response.headers);
}
}
return Promise.reject(reason);
});
}
dispatchReques发送请求的部分是在adapter里面处理的,在浏览器中的请求是通过xhr进行发送的。
// 创建xhr实例
const xhr = new XMLHttpRequest()
// 打开一个请求 get请求 路径为/path true为异步发送
xhr.open('get', '/path', true)
// 设置请求头信息
xhr.setRequestHeader('MyHeader', 'MyValue')
// 监听readyState变化
// readyState === 0 (UNSENT)没有初始化,还没调用xhr.open()方法
// readyState === 1 (OPEN) 调用xhr.open(), 还没调用xhr.send()方法
// readyState === 2 (HEADERS_RECEIVED)调用了send()方法,还没接收到服务器响应
// readState === 3 (LOADING) 接收到部分数据
// readState === 4 (DONE) 已经接收到全部数据
xhr.onreadystatechange = function () {
if(xhr.readyState !== 4) {
return
}
if(xhr.status >= 200 && xhr.status < 400) {
console.log(xhr.responseText)
} else {
// ... 异常处理
}
}
// timeout
xhr.timeout = 6000 //设置请求超时时间
xhr.ontimeout = () => {
console.log('请求超时')
}
xhr.onabort = () => {
console.log('取消请求')
}
xhr.onerror = () => {
console.log('网络异常')
}
xhr.onload = () => {
console.log('请求数据接受完成')
}
// axios是在这个事件中进行请求响应处理的
xhr.onloadend = () => {
console.log('请求结束')
}
// 发送请求 参数为请求数据没有传null
xhr.send(null)
// 取消请求
xhr.abort()
Axios请求响应处理
function onloadend() {
if (!request) {
return;
}
// Prepare the response
const responseHeaders = AxiosHeaders.from(
'getAllResponseHeaders' in request && request.getAllResponseHeaders()
);
const responseData = !responseType || responseType === 'text' || responseType === 'json' ?
request.responseText : request.response;
const response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config,
request
};
settle(function _resolve(value) {
resolve(value);
done();
}, function _reject(err) {
reject(err);
done();
}, response);
// Clean up request
request = null;
}
function settle(resolve, reject, response) {
const validateStatus = response.config.validateStatus;
if (!response.status || !validateStatus || validateStatus(response.status)) {
resolve(response);
} else {
reject(new AxiosError(
'Request failed with status code ' + response.status,
[AxiosError.ERR_BAD_REQUEST, AxiosError.ERR_BAD_RESPONSE][Math.floor(response.status / 100) - 4],
response.config,
response.request,
response
));
}
}
Retry
有了上面的axios运行原理和请求发送原理可以总结出,要是添加retry机制,可通过添加请求拦截器进行处理。
Axios进入reject情况
- Axios请求流程进入reject状态原因可以为:
-
- 请求取消
-
request.onabort = function handleAbort() { if (!request) { return; } reject(new AxiosError( 'Request aborted', AxiosError.ECONNABORTED, config, 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(new AxiosError( 'Network Error', AxiosError.ERR_NETWORK, config, request )); // Clean up request request = null; };
- 请求超时
-
request.ontimeout = function handleTimeout() { let timeoutErrorMessage = config.timeout ? 'timeout of ' + config.timeout + 'ms exceeded' : 'timeout exceeded'; const transitional = config.transitional || transitionalDefaults; if (config.timeoutErrorMessage) { timeoutErrorMessage = config.timeoutErrorMessage; } reject(new AxiosError( timeoutErrorMessage, transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED, config, request)); // Clean up request request = null; };
- response.status || validateStatus || validateStatus(response.status)
function settle(resolve, reject, response) {
const validateStatus = response.config.validateStatus;
// response.status 表示服务器响应的HTTP状态码,如果服务器没有返回状态码,这个属性就默认
// 是200 要是没有配置validateStatus的话 axios不会关心状态码
if (!response.status || !validateStatus || validateStatus(response.status)) {
resolve(response);
} else {
reject(new AxiosError(
'Request failed with status code ' + response.status,
[AxiosError.ERR_BAD_REQUEST, AxiosError.ERR_BAD_RESPONSE][Math.floor(response.status / 100) - 4],
response.config,
response.request,
response
));
}
}
response.status 表示服务器响应的HTTP状态码,如果服务器没有返回状态码,这个属性就默认是200 要是没有配置validateStatus的话 axios不会关心状态码。
测试
axios.defaults.validateStatus = (status)=>{
console.log(status,'...testing')
return false
}
axios.get('/')
.then(function (response) {
console.log(response,"-------success");
})
.catch(function (error) {
console.log(error,'-----error');
});
axios.defaults.validateStatus = (status)=>{
console.log(status,'...testing')
return true
}
axios.get('/')
.then(function (response) {
console.log(response,"-------success");
})
.catch(function (error) {
console.log(error,'-----error');
});
- 因此当没有设置validateStatus的时候进入reject状态的只有
-
- 请求取消
- 网络异常
- 请求超时
Retry机制可以针对这三种情况进行处理。
Retry拦截器
请求取消是主动结束请求,因此只需要针对请求异常、请求超时进行retry。
retry相关的属性有 请求次数、请求间隔时间。
// 把相关属性设置在axios.defaults.headers.common上
// 这里是所有请求错误都加重试机制 要是针对某一个请求需要在具体请求header上加属性
axios.defaults.headers.common['retry'] = 3 //重试请求次数
axios.defaults.headers.common['retryDelay'] = 1000 //重试请求间隔时间
axios.interceptors.response.use(undefined, err => {
const config = err.config;
if ((!config || !config.headers.retry)&&
// 请求超时错误
(error.message.indexOf('timeout')==-1
// 网络异常错误
||error.message.indexOf('Network Error')==-1)
) {
return Promise.reject(err);
}
// 第一次重试设置_retryCount=0
config.headers._retryCount = config.headers._retryCount || 0;
// 请求次数超过设置的次数
if (config.headers._retryCount >= config.headers.retry) {
return Promise.reject(err);
}
// 请求次数加一
config.headers._retryCount += 1;
// 设置请求间隔 通过定时器来阻塞
const backoff = new Promise(function (resolve) {
setTimeout(function () {
resolve();
}, config.headers.retryDelay || 1);
});
return backoff.then(function () {
// 请求重试
return axios(config);
});
});
测试(这里没有限制错误类型哈)
axios.defaults.validateStatus = (status)=>{
console.log(status,'...testing')
return false
}
axios.get('/',{
headers:{
retry:3,// 重试3次
retryDelay:1000 // 重试间隔1s
}
})
.then(function (response) {
console.log(response,"-------success");
})
.catch(function (error) {
console.log(error,'-----error');
});