版本
v0.26.1 官方文档
项目架构
.
├── dist ## 项目最终打包数据目录
│ ├── axios.js
│ ├── axios.map
│ ├── axios.min.js
│ └── axios.min.map
├── index.d.ts ## ts类型声明输出
├── index.js ## 入口文件
├── lib ## 项目源码库
├── adapters ## 定义发送请求的适配器
│ ├── http.js
│ └── xhr.js
├── axios.js ## 请求入口,导出方法
├── cancel ## 定义取消请求方法
│ ├── Cancel.js
│ ├── CancelToken.js
│ └── isCancel.js
├── core # 核心包
│ ├── Axios.js
│ ├── InterceptorManager.js
│ ├── buildFullPath.js
│ ├── createError.js
│ ├── dispatchRequest.js
│ ├── enhanceError.js
│ ├── mergeConfig.js
│ ├── settle.js
│ └── transformData.js
├── defaults ## 默认配置
│ ├── index.js
│ └── transitional.js
├── env ## env info
│ └── data.js
├── helpers ## 定义辅助方法
│ ├── bind.js
│ ├── buildURL.js
│ ├── combineURLs.js
│ ├── cookies.js
│ ├── deprecatedMethod.js
│ ├── isAbsoluteURL.js
│ ├── isAxiosError.js
│ ├── isURLSameOrigin.js
│ ├── normalizeHeaderName.js
│ ├── parseHeaders.js
│ ├── spread.js
│ ├── toFormData.js
│ └── validator.js
└── utils.js ## 常用工具方法
XMLHttpRequest 常用重要Api
onreadystatechangereadyStateresponseTyperesponsestatussetRequestHeaderabortgetAllResponseHeaders
思考
分析源码前,我们从对外Api开始入口,思考使用的几种方式,一些场景 从上文axios介绍了解到,大概有以下几种方法
- 调用方式
// 函数执行
axios(config)
// 方法调用
axios[method](config)
// 创建实例
const instance = axios.create(config);
...
...
- 拦截器
- 请求拦截器
- 响应拦截器
const instance = axios.create({ timeout: 20, }) instance.interceptors.request.use( (cinfig) => { return config }, (err) => Promise.reject(err), ) instance.interceptors.response.use( (response) => { return response }, (err) => Promise.reject(err), ) - 取消请求
- axios.cancelToken
- AbortController
- axios.all / axios.spread
Axios执行顺序
Default 默认配置
var utils = require('../utils');
var normalizeHeaderName = require('../helpers/normalizeHeaderName');
var enhanceError = require('../core/enhanceError');
var transitionalDefaults = require('./transitional');
var DEFAULT_CONTENT_TYPE = {
'Content-Type': 'application/x-www-form-urlencoded'
};
function setContentTypeIfUnset(headers, value) {
if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) {
headers['Content-Type'] = value;
}
}
// 根据当前环境是否存在XMLHttpRequest对象,执行不同适配器方法
function getDefaultAdapter() {
var adapter;
if (typeof XMLHttpRequest !== 'undefined') {
// 浏览器环境
adapter = require('../adapters/xhr');
} else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
// node环境
adapter = require('../adapters/http');
}
return adapter;
}
function stringifySafely(rawValue, parser, encoder) {
if (utils.isString(rawValue)) {
try {
(parser || JSON.parse)(rawValue);
return utils.trim(rawValue);
} catch (e) {
if (e.name !== 'SyntaxError') {
throw e;
}
}
}
return (encoder || JSON.stringify)(rawValue);
}
var defaults = {
transitional: transitionalDefaults,
// 根据当前运行环境获取请求适配器
adapter: getDefaultAdapter(),
// 改变请求data
transformRequest: [function transformRequest(data, headers) {
// 标准化headers中Accept、Content-Type参数
normalizeHeaderName(headers, 'Accept');
normalizeHeaderName(headers, 'Content-Type');
if (utils.isFormData(data) ||
utils.isArrayBuffer(data) ||
utils.isBuffer(data) ||
utils.isStream(data) ||
utils.isFile(data) ||
utils.isBlob(data)
) {
return data;
}
if (utils.isArrayBufferView(data)) {
return data.buffer;
}
if (utils.isURLSearchParams(data)) {
setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
return data.toString();
}
if (utils.isObject(data) || (headers && headers['Content-Type'] === 'application/json')) {
setContentTypeIfUnset(headers, 'application/json');
return stringifySafely(data);
}
return data;
}],
// 改变响应数据
transformResponse: [function transformResponse(data) {
var transitional = this.transitional || defaults.transitional;
var silentJSONParsing = transitional && transitional.silentJSONParsing;
var forcedJSONParsing = transitional && transitional.forcedJSONParsing;
var strictJSONParsing = !silentJSONParsing && this.responseType === 'json';
if (strictJSONParsing || (forcedJSONParsing && utils.isString(data) && data.length)) {
try {
return JSON.parse(data);
} catch (e) {
if (strictJSONParsing) {
if (e.name === 'SyntaxError') {
throw enhanceError(e, this, 'E_JSON_PARSE');
}
throw e;
}
}
}
return data;
}],
// 设置超时时间,默认0不会超时
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
maxBodyLength: -1,
// 定义给定的http状态码,判断是执行resolve还是Promis.reject
validateStatus: function validateStatus(status) {
return status >= 200 && status < 300;
},
headers: {
common: {
'Accept': 'application/json, text/plain, */*'
}
}
};
utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) {
defaults.headers[method] = {};
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE);
});
module.exports = defaults;
入口axios.js
'use strict';
var utils = require('./utils');
var bind = require('./helpers/bind');
var Axios = require('./core/Axios');
var mergeConfig = require('./core/mergeConfig');
var defaults = require('./defaults');
/**
* Create an instance of Axios
*
* @param {Object} defaultConfig The default config for the instance
* @return {Axios} A new instance of Axios
*/
function createInstance(defaultConfig) {
// 创建Axios实例化对象,包含默认配置<defaults>以及拦截器<this.interceptors>、
// 挂在原型对象上的请求方法<Axios.prototype.request>
var context = new Axios(defaultConfig);
// 创建新的实例对象, bind返回一个函数,函数内部执行Axios.prototype.request同时函数上下文为context
var instance = bind(Axios.prototype.request, context);
// 复制Axios.prototype原型对象上属性、方法给新的实例insatance
// utisl.extend内部实现, 如果是复制是方法,那么改变方法内部函数执行上下文为context
utils.extend(instance, Axios.prototype, context);
// 复制context属性方法给instance
utils.extend(instance, context);
// 工厂方法,支持通过create传入自定义配置
instance.create = function create(instanceConfig) {
return createInstance(mergeConfig(defaultConfig, instanceConfig));
};
return instance;
}
// 创建默认导出的实例对象
var axios = createInstance(defaults);
// 暴露Axios允许去继承???
axios.Axios = Axios;
// 暴露CancelToken/isCancel/VERSION 方法
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
axios.VERSION = require('./env/data').version;
// 暴露 all/spread方法, 类似Promise.all功能
axios.all = function all(promises) {
return Promise.all(promises);
};
axios.spread = require('./helpers/spread');
// 暴露 isAxiosError方法
axios.isAxiosError = require('./helpers/isAxiosError');
module.exports = axios;
// 允许在ts中使用默认导入
module.exports.default = axios;
入口文件axios.js大致流程
- 利用构造函数Axios,实例化对象context,context包含一些默认配置defaults和管理拦截器
- 创建新的实例对象instance,同时把Axios.prototype.request执行上下文改造为context
- 将Axios.prototype原型对象属性及方法复制到intance上,方法中this指向了context
- 把context上默认属性以及拦截器复制到instance上
- 利用工厂函数,创建新的实例,用户可以传入自定义配置
Axios.js
'use strict';
var utils = require('./../utils');
var buildURL = require('../helpers/buildURL');
var InterceptorManager = require('./InterceptorManager');
var dispatchRequest = require('./dispatchRequest');
var mergeConfig = require('./mergeConfig');
var validator = require('../helpers/validator');
var validators = validator.validators;
// Axios构造函数,传入默认配置,拥有拦截器对象
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
// 核心方法 派发请求
Axios.prototype.request = function request(configOrUrl, config) {
// 处理config, configUrl
if (typeof configOrUrl === 'string') {
config = config || {};
config.url = configOrUrl;
} else {
config = configOrUrl || {};
}
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';
}
var transitional = config.transitional;
if (transitional !== undefined) {
validator.assertOptions(transitional, {
silentJSONParsing: validators.transitional(validators.boolean),
forcedJSONParsing: validators.transitional(validators.boolean),
clarifyTimeoutError: validators.transitional(validators.boolean)
}, false);
}
// 请求拦截器数组
var requestInterceptorChain = [];
// 默认设置是同步请求拦截器
var synchronousRequestInterceptors = true;
// 遍历注入的请求拦截器: 内部调用utils.forEach过滤掉原型对象上属性,只剩this.handles拦截器数组
// this.interceptors.request === new InterceptorManager()
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
return;
}
// 对外抛出异步||同步配置,通过synchronous标志拦截器是同步还是异步,默认是异步
synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// 响应拦截器数组
var responseInterceptorChain = [];
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
var promise;
// 组成promise调用链
// 异步请求拦截器,默认情况
if (!synchronousRequestInterceptors) {
var chain = [dispatchRequest, undefined];
Array.prototype.unshift.apply(chain, requestInterceptorChain);
chain = chain.concat(responseInterceptorChain);
promise = Promise.resolve(config);
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
}
// 同步请求拦截器,一次调用请求拦截器,得到最后处理后newCondfig
var newConfig = config;
while (requestInterceptorChain.length) {
var onFulfilled = requestInterceptorChain.shift();
var onRejected = requestInterceptorChain.shift();
try {
newConfig = onFulfilled(newConfig);
} catch (error) {
onRejected(error);
break;
}
}
// 发起请求
try {
promise = dispatchRequest(newConfig);
} catch (error) {
return Promise.reject(error);
}
while (responseInterceptorChain.length) {
promise = promise.then(responseInterceptorChain.shift(), responseInterceptorChain.shift());
}
return promise;
};
// 获取uri函数
Axios.prototype.getUri = function getUri(config) {
config = mergeConfig(this.defaults, config);
return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, '');
};
// 依次遍历方法别名,Axios.prototype原型对象别名请求,核心都是调用Axios.prototype.request
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
Axios.prototype[method] = function(url, config) {
return this.request(mergeConfig(config || {}, {
method: method,
url: url,
data: (config || {}).data
}));
};
});
// post, put, patch 请求需要请求体<data>, 分开处理
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
Axios.prototype[method] = function(url, data, config) {
return this.request(mergeConfig(config || {}, {
method: method,
url: url,
data: data
}));
};
});
module.exports = Axios;
Axios.j大致流程
- 构造函数可以实例化对象,挂在defaults,拦截器对象
- Axios.prototype挂在别名方法,内部都是调用this.request
- 组装promise调用链
var promiseChain = [
'请求成功拦截2', '请求失败拦截2',
'请求成功拦截1', '请求失败拦截1',
'dispatch', 'undefined',
'响应成功拦截1', '响应失败拦截1',
'响应成功拦截2', '响应失败拦截2',
]
- 调用Dispatch
Promise调用链
拦截器
'use strict';
var utils = require('./../utils');
function InterceptorManager() {
this.handlers = [];
}
// 添加拦截器到this.handlers, 返回当前索引值,用作移除拦截器使用
// const current = axios.interceptors.request.use(fn);
// axios.interceptors.request.eject(current);
InterceptorManager.prototype.use = function use(fulfilled, rejected, options) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected,
synchronous: options ? options.synchronous : false,
runWhen: options ? options.runWhen : null
});
return this.handlers.length - 1;
};
// 移除拦截器
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
};
// 遍历所有非空注册拦截器,
// 拦截器被移除后 this.handlers[id] = null; 拦截器数组被移除项索引为null,需要过滤掉
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
};
module.exports = InterceptorManager;
异步拦截器执行顺序
const fetch = async () => {
try {
const res = await instance({ url: '/api' })
console.log(res)
} catch (err) {
console.log('fn', err)
}
}
const instance = axios.create()
instance.interceptors.request.use(
async (config) => {
console.log('第1个请求拦截器执行 async start')
const res = await new Promise((resolve) => {
setTimeout(() => {
resolve(true)
}, 5000)
})
console.log('第1个请求拦截器执行 async end')
if (!res) {
return Promise.reject(new Error('request1 error'))
}
return config
},
(err) => {
console.log('第1个请求拦截器', err)
return Promise.reject(err)
},
)
instance.interceptors.request.use(
async (config) => {
console.log('第2个请求拦截器执行 async start')
const res = await new Promise((resolve) => {
setTimeout(() => {
resolve(true)
}, 5000)
})
console.log('第2个请求拦截器执行 async end')
if (!res) {
return Promise.reject(new Error('request2 error'))
}
return config
},
(err) => {
console.log('第2个请求拦截器', err)
return Promise.reject(err)
},
)
instance.interceptors.response.use(
(res) => {
console.log('第1个响应拦截器', res)
return res
},
(err) => Promise.reject(err),
)
instance.interceptors.response.use(
(res) => {
console.log('第2个响应拦截器', res)
return res
},
(err) => Promise.reject(err),
)
**请求拦截器全部成功**
// 第2个请求拦截器执行 async start
// 第2个请求拦截器执行 async end
// 第1个请求拦截器执行 async start
// 第1个请求拦截器执行 async end
...10s后发起请求
// 第1个响应拦截器 {data: {…}, status: 200, statusText: 'OK', headers: {…}, config: {…}, …}
// 第2个响应拦截器 {data: {…}, status: 200, statusText: 'OK', headers: {…}, config: {…}, …}
**第二个拦截器reject**
// 第2个请求拦截器执行 async start
// 第2个请求拦截器执行 async end
...delay 5s
// 第1个请求拦截器 Error: request2 error
**第一个拦截器reject**
// 第2个请求拦截器执行 async start
// 第2个请求拦截器执行 async end
...delay 5s
// 第1个请求拦截器执行 async start
// 第1个请求拦截器执行 async end
...delay 5s
// fn Error: request1 error
同步拦截器执行顺序
const instance = axios.create()
instance.interceptors.request.use(
(config) => {
console.log('第1个请求拦截器执行 async start')
const res = true
if (!res) {
throw new Error('request1 error')
}
return config
},
(err) => {
console.log('第1个请求拦截器', err)
throw new Error(err)
},
{
synchronous: true,
},
)
instance.interceptors.request.use(
(config) => {
console.log('第2个请求拦截器执行 async start')
const res = true
if (!res) {
throw new Error('request2 error')
}
return config
},
(err) => {
console.log('第2个请求拦截器', err)
throw new Error(err)
},
{
synchronous: true,
},
)
instance.interceptors.response.use(
(res) => {
console.log('第1个响应拦截器', res)
return res
},
(err) => Promise.reject(err),
)
instance.interceptors.response.use(
(res) => {
console.log('第2个响应拦截器', res)
return res
},
(err) => Promise.reject(err),
)
**都是true**
// 第2个请求拦截器执行 async start
// 第1个请求拦截器执行 async start
// 第1个响应拦截器
// 第2个响应拦截器
**第二个拦截false**
// 第2个请求拦截器执行 async start
// 第2个请求拦截器 Error: request2 error
// fn Error: Error: request2 error // 是外部方法捕获
**第一个拦截false**
// 第2个请求拦截器执行 async start
// 第1个请求拦截器执行 async start
// 第1个请求拦截器 Error: request1 error
// fn Error: Error: request1 error // 是外部方法捕获
Dispatch派发请求
'use strict';
var utils = require('./../utils');
var transformData = require('./transformData');
var isCancel = require('../cancel/isCancel');
var defaults = require('../defaults');
var Cancel = require('../cancel/Cancel');
// 如果已经取消,直接抛出原因
function throwIfCancellationRequested(config) {
if (config.cancelToken) {
config.cancelToken.throwIfRequested();
}
if (config.signal && config.signal.aborted) {
throw new Cancel('canceled');
}
}
// 派发请求,可以使用自定义适配器
module.exports = function dispatchRequest(config) {
throwIfCancellationRequested(config);
// 确保header是存在
config.headers = config.headers || {};
// 转换请求数据,post
config.data = transformData.call(
config,
config.data,
config.headers,
config.transformRequest
);
// 抹平header
config.headers = utils.merge(
config.headers.common || {},
config.headers[config.method] || {},
config.headers
);
// 删除header上挂在的方法
utils.forEach(
['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
function cleanHeaderConfig(method) {
delete config.headers[method];
}
);
// 使用默认适配器
var adapter = config.adapter || defaults.adapter;
// 自定义适配器,方法接收config参数,返回Promise
return adapter(config).then(function onAdapterResolution(response) {
throwIfCancellationRequested(config);
// 转换响应数据
response.data = transformData.call(
config,
response.data,
response.headers,
config.transformResponse
);
return response;
}, function onAdapterRejection(reason) {
// 如果是手动取消请求
if (!isCancel(reason)) {
throwIfCancellationRequested(config);
// 转化数据
if (reason && reason.response) {
reason.response.data = transformData.call(
config,
reason.response.data,
reason.response.headers,
config.transformResponse
);
}
}
return Promise.reject(reason);
});
};
Dispacth主要做了下面几件事情
- 如果已经取消请求,则取消请求,throw 原因,promise走向reject
- 转化请求数据
- 抹平header,删除header上一些方法
- 使用适配器请求发起请求,返回Promis实例
Adapter 适配器
'use strict';
var utils = require('./../utils');
var settle = require('./../core/settle');
var cookies = require('./../helpers/cookies');
var buildURL = require('./../helpers/buildURL');
var buildFullPath = require('../core/buildFullPath');
var parseHeaders = require('./../helpers/parseHeaders');
var isURLSameOrigin = require('./../helpers/isURLSameOrigin');
var createError = require('../core/createError');
var transitionalDefaults = require('../defaults/transitional');
var Cancel = require('../cancel/Cancel');
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
var requestData = config.data;
var requestHeaders = config.headers;
var responseType = config.responseType;
var onCanceled;
function done() {
if (config.cancelToken) {
config.cancelToken.unsubscribe(onCanceled);
}
if (config.signal) {
config.signal.removeEventListener('abort', onCanceled);
}
}
if (utils.isFormData(requestData)) {
delete requestHeaders['Content-Type'];
}
var request = new XMLHttpRequest();
// http 授权验证相关
if (config.auth) {
var username = config.auth.username || '';
var password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : '';
requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
}
// 拼接完整路径
var fullPath = buildFullPath(config.baseURL, config.url);
// 序列化请求params参数,eg:get请求params参数
request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);
// 设置超时时间
request.timeout = config.timeout;
// 当请求结束时触发, 无论请求成还是失败<abort||error>
function onloadend() {
if (!request) {
return;
}
// 生成数据
var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;
var responseData = !responseType || responseType === 'text' || responseType === 'json' ?
request.responseText : request.response;
var response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config: config,
request: request
};
// 见utils.settle函数。 对于给定状态码判断响应是成功还是失败
settle(function _resolve(value) {
resolve(value);
done();
}, function _reject(err) {
reject(err);
done();
}, response);
// 请求结束清除request
request = null;
}
if ('onloadend' in request) {
request.onloadend = onloadend;
} else {
// 监听http状态值去仿真onloadend
request.onreadystatechange = function handleLoad() {
if (!request || request.readyState !== 4) {
return;
}
// 对于大数据请求错误并不会低到response, 请求错误在request.onerror事件捕获
// 但是有一个例外: 使用file协议的请求,大多数浏览器即使请求成功,也会状态码为0
if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
return;
}
// readystate handler is calling before onerror or ontimeout handlers,
// so we should call onloadend on the next 'tick'
setTimeout(onloadend);
};
}
// request被停止时触发
request.onabort = function handleAbort() {
if (!request) {
return;
}
reject(createError('Request aborted', config, 'ECONNABORTED', request));
request = null;
};
// 处理请求错误
request.onerror = function handleError() {
reject(createError('Network Error', config, null, request));
request = null;
};
// 处理超时
request.ontimeout = function handleTimeout() {
var timeoutErrorMessage = config.timeout ? 'timeout of ' + config.timeout + 'ms exceeded' : 'timeout exceeded';
var transitional = config.transitional || transitionalDefaults;
if (config.timeoutErrorMessage) {
timeoutErrorMessage = config.timeoutErrorMessage;
}
reject(createError(
timeoutErrorMessage,
config,
transitional.clarifyTimeoutError ? 'ETIMEDOUT' : 'ECONNABORTED',
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;
}
}
// 添加请求头
if ('setRequestHeader' in request) {
utils.forEach(requestHeaders, function setRequestHeader(val, key) {
// requestData是undefined情况下,移除content-type
if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type')
delete requestHeaders[key];
} else {
request.setRequestHeader(key, val);
}
});
}
// 传递cookie
if (!utils.isUndefined(config.withCredentials)) {
request.withCredentials = !!config.withCredentials;
}
// 添加responseType
if (responseType && responseType !== 'json') {
request.responseType = config.responseType;
}
// 下载进度处理时间:在请求完整之前周期性调用callback,
// 可以用来判断当前请求进度,只做进度条
if (typeof config.onDownloadProgress === 'function') {
request.addEventListener('progress', config.onDownloadProgress);
}
// 上传进度处理事件: 并不是所有浏览器都支持
if (typeof config.onUploadProgress === 'function' && request.upload) {
request.upload.addEventListener('progress', config.onUploadProgress);
}
// 处理拦截器
if (config.cancelToken || config.signal) {
onCanceled = function(cancel) {
if (!request) {
return;
}
reject(!cancel || (cancel && cancel.type) ? new Cancel('canceled') : cancel);
request.abort();
request = null;
};
config.cancelToken && config.cancelToken.subscribe(onCanceled);
if (config.signal) {
config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
}
}
if (!requestData) {
requestData = null;
}
// 发送请求
request.send(requestData);
});
};
适配器主要做以下事情
- 新建XMLHttpRequest实例
- 拼接完整路径,处理header,生成响应数据
- 设置request.onerror、ontimeout事件处理器
- 对onreadystatechange || request.onloaded事件监听
- 配置onUploadProgress、onDownloadProgress处理上传和下载进度
- 提供取消功能,调用原生abort方法
拦截器CancelToken
xhs.js取消请求部分代码
if (config.cancelToken || config.signal) {
onCanceled = function(cancel) {
if (!request) {
return;
}
reject(!cancel || (cancel && cancel.type) ? new Cancel('canceled') : cancel);
request.abort();
request = null;
};
// 配置cancelToken,则把onCanceled函数维护在实例化CancelToken对象的订阅者列表里面
config.cancelToken && config.cancelToken.subscribe(onCanceled);
// 不支持外部传入自定义输入参数
if (config.signal) {
// config.signal.aborted 表示与之通信的请求是否被中止, 中止返回true, 否则false
// 倘若进入请求已经中止则直接调用onCanceled(), 否则信号对象abortSignal添加监听事件abort
// 中止器对象AbortController调用abort方法触发信号对象abortSignal的abort监听函数, 进入执行onCanCanl方法
config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
}
}
'use strict';
var Cancel = require('./Cancel');
// CanToken 构造函数,接受executor<执行方法>函数作为参数
function CancelToken(executor) {
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.');
}
var resolvePromise;
// 创建promise实例
this.promise = new Promise(function promiseExecutor(resolve) {
// resolve方法提出来,resolvePromise执行时,this.promis状态变成fullfilled
resolvePromise = resolve;
});
var token = this;
// resolvePromise执行,进行then阶段
this.promise.then(function(cancel) {
if (!token._listeners) return;
var i;
var l = token._listeners.length;
// 遍历订阅列表
// 将cancel取消原因执行订阅函数
// 订阅者是 xhr中 存入的config.cancelToken && config.cancelToken.subscribe(onCanceled);
for (i = 0; i < l; i++) {
token._listeners[i](cancel);
}
token._listeners = null;
});
this.promise.then = function(onfulfilled) {
var _resolve;
var promise = new Promise(function(resolve) {
token.subscribe(resolve);
_resolve = resolve;
}).then(onfulfilled);
promise.cancel = function reject() {
token.unsubscribe(_resolve);
};
return promise;
};
// 立即执行实例化过程中传入的函数。把取消函数cancel抛给外部,把取消时机给用户
// 执行cancel函数
executor(function cancel(message) {
// 如果已经取消,则返回
if (token.reason) {
return;
}
// 调用new Cancel(message),有__CANCEL__属性,标志主动取消请求,可自定义取消信息,赋值给token<this>上reson
token.reason = new Cancel(message);
// 执行this.promise中resolve。promise变成完成态
resolvePromise(token.reason);
});
}
// 已经取消则返回取消原因
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
if (this.reason) {
throw this.reason;
}
};
// 订阅取消信号,把取消函数存放在this._listeners订阅数组中
CancelToken.prototype.subscribe = function subscribe(listener) {
if (this.reason) {
listener(this.reason);
return;
}
if (this._listeners) {
this._listeners.push(listener);
} else {
this._listeners = [listener];
}
};
// 取消订阅
CancelToken.prototype.unsubscribe = function unsubscribe(listener) {
if (!this._listeners) {
return;
}
var index = this._listeners.indexOf(listener);
if (index !== -1) {
this._listeners.splice(index, 1);
}
};
// 返回一个对象对象:对象由CancelToken实例化对象和cancel函数组成
// 最终要分析实例化CancelToken过程中executor执行规则
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel
};
};
module.exports = CancelToken;
取消请求方法
CancelToken
// 方式一: CancelToken.source();
const source = cancelToken.source()
const fetch = async () => {
try {
const res = await axios({
url: '/api',
params: {
name: 1,
},
cancelToken: source.token,
onDownloadProgress: (event) => {
console.log(event)
},
})
console.log(res)
} catch (err) {
if (axios.isCancel(err)) {
console.log('cancel error')
}
console.log('fn', err)
}
}
const cancel = () => {
source.cancel()
}
// 方式二: 构造函数实例化
const fetch = async () => {
try {
const res = await axios({
url: '/api',
params: {
name: 1,
},
cancelToken: new CancelToken((c) => {
cancel = c
}),
onDownloadProgress: (event) => {
console.log(event)
},
})
console.log(res)
} catch (err) {
if (axios.isCancel(err)) {
console.log('cancel error')
}
console.log('fn', err)
}
}
const cancelFetch = () => {
cancel()
}
AbortController
const Controller = new AbortController()
const fetch = async () => {
try {
const res = await axios({
url: '/api',
params: {
name: 1,
},
signal: Controller.signal,
onDownloadProgress: (event) => {
console.log(event)
},
})
console.log(res)
} catch (err) {
if (axios.isCancel(err)) {
console.log('cancel error')
}
console.log('fn', err)
}
}
const cancelFetch = () => {
Controller.abort()
}
总结
CancelToken构造函数内部执行会创建一个promise实例和一个reason存储取消信息,实例化过程执行executor,executor会把取消方法抛给外部,当外部调用取消方法。promise进入完成态,执行订阅列表<这个订阅列表是xhr请求创建过程中维护的>。当订阅函数执行,xhr中promise调用链执行reject并调用原生方法abort取消请求
signal
从v0.22.0开始,axios支持以fetch api传入取消信号sinal<AbortController构造函数实例>中止请求
CancelToken以及signal都是间接调用request.abort()中止请求。
AbortController
AbortController接口是一个控制器对象,允许你根据需要中止一个或多个web请求
描述: 调用AbortController构造函数,得到实例化对象 中止器对象abortController。 在fetch请求中,将abortController.signal变量添加到请求体中。使用abortController.abort()中止请求;
- const abortController = new AbortController() 中止请求对象 => 中止器对象
- abortSignal = abortController.signal 信号对象。挂在属性aborted && 设置abort监听函数
- 发出请求 abortSignal传入fetch/axios/... 请求体 {signal: abortSignal}
// 取消重复请求: 例如弱网情况下,频繁切换Tab选项卡,或者频繁Input输入请求
const abortController = useRef<AbortController>(null)
console.log('dashboard')
const fetch = async () => {
if (abortController.current?.signal) {
abortController.current.abort()
}
try {
abortController.current = new AbortController()
const res = await axios({
url: '/api',
params: {
name: 1,
},
signal: abortController.current.signal,
onDownloadProgress: (event) => {
console.log(event)
},
})
console.log(res)
abortController.current = null
} catch (err) {
if (axios.isCancel(err)) {
console.log('cancel error')
}
console.log('fn', err)
}
}