axios源码记录
前言
最近都再看算法的内容,突然发现自己axios方面的知识还没有学习好,也攒下了不少偏笔记,在这里浅浅记录一下自己学习的历程。
axios 初始化
当我们npm install axios
后,打开axios的 package.json
可以找得到axios的入口文件 "main": "index.js"
我们顺着这个文件去一步步理清axios的内容
index.js
随之我们进入index.js
的文件中
axios.js
引入部分
文件最上面的部分是各种文件的引入,包括配默认置文件,工具函数,Axios主文件
下面的部分中可以看得出来,axios是一个函数。
是生成实例对象
axios
、axios.Axios
、axios.create
等。
函数的内容如图
- 定义了一个
Axios实例
- 使用
bind函数
创建instance
,返回一个新的 wrap 函数, - 使用
extend函数
将Axios原型和实例上的内容绑定到instance
上,- 也就是为什么 有 axios.get 等别名方法
- 也就是为什么默认配置
axios.defaults
和拦截器axios.interceptors
可以使用的原因
- 定义调用
axios.create()
时执行的内容 - 最后返回实例对象
剩下的文件中的部分就是为 axios函数
添加方法
取消相关API实现,还有
all
、spread
、导出等实现。
这些还没有去研究,之后再写吧
工具函数
源码中有一些自定义的工具函数,和平时我们认知的有一点不一样,把他们单独拿出来讲一下
bind函数
这里重写的bind函数并不像我们平时的bind函数一样,参数的位置有一丢丢不一样,但也容易理解
utils.extend
我们再utils
文件中找到extend
项
forEach
加了大量的注释,应该一下子就能看懂了,
调用forEach时
forEach(遍历的内容,处理的函数(值,索引,遍历的内容){处理细节}, 是否全是自身的key)
看完这三个 方法工具 上面的
createInstance函数
应该就能看懂了
merge
把多个对象的值都放到一起来。
核心–构造函数 Axios
在上面的axios.js
的文件中
这一步是非常关键的,其中出现了很多次的 Axios
。同时这也是 axios的关键所在
Axios构造函数
Axios文件中主要内容在这个Axios构造函数的构建。
里面包括了
- 调用
axios('url',config)
,axios({config})
的配置方式 - 默认配置和传入配置的合并
- 请求方法的设置
- 请求头配置
- 拦截器注册
- 拦截器操作的promise
- 将
get,post,delete
等方法绑定到实例的原型上 - 暴露处Axios
这些内容,如果把这些内容掌握了axios就差不多了
配置对象config的处理
无论是直接通过axios('url',{config})
的方法调用 axios 还是 axios({config})
或者是 axios.get()
的方式使用axios , 都绕不过config的配置
直接调用axios({})或者axios(“url”,{})
使用这两种方式时,原理上就是直接调用Axios的request方法
这时会对调用时,传入的参数进行判断,
如果第一个参数是字符串形式 即用 axios("url",{})
的方法,就会将第一个参数中的 url 复制到 第二个参数的config中
如果第一个字符串不存在 即用 axios({})
的方式,就将第一个参数的值,直接给了config,
之后还要将传入配置和axios默认配置进行合并。
通过 axios.get()
调用
Axios的原型中绑定了一些列的请求方法,调用这些方法会返回一个 this.request同样也是执行请求
,会将调用时传入的一系列参数都合并起来,组成一个 config对象
mergeConfig.js
mergeConfig中有争对不同的配置型,确定合并的方式,有一个Map的映射表,还有处理的forEach
将所有的配置文件(用户传入的,默认配置的等等)进行一个合并,最终返回合并后的结果
request()
在Axios.js
中,Axios构造函数中的 request()
时最重要的模块,下面是我对源码详细注释后的 request
实力函数
request(configOrUrl, config) {
/*eslint no-param-reassign:0*/
// Allow for axios('example/url'[, config]) a la fetch API
// 对url的设置,争对调用axios时是否将 url 分出去,做出区分配置
if (typeof configOrUrl === 'string') {
config = config || {};
config.url = configOrUrl;
} else {
config = configOrUrl || {};
}
//将传入的配置和默认配置合并
// mergeConfig 会将参数中的配置合并在一起,
config = mergeConfig(this.defaults, config);
const 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);
}
// Set config.method
// 确定 config 中的方法名
config.method = (config.method || this.defaults.method || 'get').toLowerCase();
// 下面的这堆都是对header进行处理,最后生成请求用的headers
// Flatten headers
const defaultHeaders = config.headers && utils.merge(
config.headers.common,
config.headers[config.method]
);
defaultHeaders && utils.forEach(
['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
function cleanHeaderConfig(method) {
delete config.headers[method];
}
);
config.headers = new AxiosHeaders(config.headers, defaultHeaders);
// filter out skipped interceptors
const requestInterceptorChain = [];
// 同步请求拦截器
let synchronousRequestInterceptors = true;
// 这里的 forEach 不是数组的forEach方法,而是 request 创建的实例 InterceptorManager 中的 实例方法forEach,
// 遍历的是其中的实例属性 handlers数组。下面的响应拦截器也是一样的
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
// 如果用户没有特殊的进行配置的话这里一般不会执行
if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
return;
}
// interceptor.synchronous 是用户 通过 axios.interceptors.request.use 注册时填入的第三个参数 option中的 synchronous 值,一般默认为false
// 表示不是同步全球拦截器
synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
// 在这里将 请求拦截器的内容添加到 chrequestInterceptorChain 中,一组一组的添加
// 请求拦截器的添加是通过 unshift 添加到 chain 的前面的
// 由于这种unshift的方法,后注册的请求拦截器会 比 先注册的拦截器的执行快
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
});
const responseInterceptorChain = [];
// 在这里将 响应拦截器的内容添加到 responseInterceptorChain 中,一组一组的添加
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
// 这里也是将 response 中的内容全部添加到 chain 的后面
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
let promise;
let i = 0;
let len;
// 这里的 synchronousRequestInterceptors 同步请求拦截器 一般指都为 false,然后执行里面的内容
if (!synchronousRequestInterceptors) {
// 先把 chain 初始化,第一个是请求,第二个是undefined
const chain = [dispatchRequest.bind(this), undefined];
// 然后在前面添加 请求拦截器的 reject和resolve
chain.unshift.apply(chain, requestInterceptorChain);
// 然后在后面添加 请求拦截器的 reject和resolve
chain.push.apply(chain, responseInterceptorChain);
len = chain.length;
// 创建一个成功的 promise 并把 合并好的config 作为参数传给.then的成功回调
// 这个config 参数就是 给到所有拦截器的config参数
promise = Promise.resolve(config);
while (i < len) {
// 最后再把这些内容 依次调用.then执行,
// 把 chain中的内容, 一组一组的(拦截器的成功回调和失败回调为一组),添加到.then的 resolve函数和reject函数中
promise = promise.then(chain[i++], chain[i++]);
}
// 最终返回一个这个promise对象
return promise;
}
// 从这里往下就是设置的是 synchronousRequestInterceptors 为true时执行的内容
// 会以同步的方式执行拦截器,而不是 .then 后放入异步队列
len = requestInterceptorChain.length;
let newConfig = config;
i = 0;
while (i < len) {
const onFulfilled = requestInterceptorChain[i++];
const onRejected = requestInterceptorChain[i++];
try {
newConfig = onFulfilled(newConfig);
} catch (error) {
onRejected.call(this, error);
break;
}
}
///
try {
//dispatchRequest
promise = dispatchRequest.call(this, newConfig);
} catch (error) {
return Promise.reject(error);
}
i = 0;
len = responseInterceptorChain.length;
while (i < len) {
promise = promise.then(responseInterceptorChain[i++], responseInterceptorChain[i++]);
}
return promise;
}
我们将如上的内容解读完后,了解到这一步中就是形成了一个 promise链,其中的 dispatchRequest 才是请求的更深层。
dispatchRequest
dispatchRequest.js
dispatchRequest中主要是对 配置data 和 返回的data的处理,发送请求时在 adapter 里面进一步划分的,
值得注意的内容是 config.transformRequest
中一系列函数 对data content-type的处理
adapter
在 defaults/index.js中我们可以找到 adapter的内容
我们顺着这个线索一步步往下
同样在 defaults/index.js 中
这里就是通过两个if
语句来判断当前的环境是 浏览器环境还是node环境 。通常都是使用 xhr 在浏览器端发送请求
adapters/index.js
最后让我们进入到 xhr 的请求中看一看具体的发送请求过程
xhr.js
这里是我们真正发起请求的地方
export default function xhrAdapter(config) {
// 请求返回一个promise对象
return new Promise(function dispatchXhrRequest(resolve, reject) {
// 在这里获取config中的data内容,配置requestHeaders,responseType等等
let requestData = config.data;
const requestHeaders = AxiosHeaders.from(config.headers).normalize();
const responseType = config.responseType;
let onCanceled;
function done() {
if (config.cancelToken) {
config.cancelToken.unsubscribe(onCanceled);
}
if (config.signal) {
config.signal.removeEventListener('abort', onCanceled);
}
}
if (utils.isFormData(requestData) && platform.isStandardBrowserEnv) {
requestHeaders.setContentType(false); // Let the browser set it
}
// 在这里我们就嫩见到我们所熟知的 ajax 请求的部分 new XMLHttpRequest
let request = new XMLHttpRequest();
// HTTP basic authentication
if (config.auth) {
const username = config.auth.username || '';
const password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : '';
requestHeaders.set('Authorization', 'Basic ' + btoa(username + ':' + password));
}
// 构建一个完整的路径
const fullPath = buildFullPath(config.baseURL, config.url);
// 发起初始化一个ajax请求,method url、参数、序列化函数
request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);
// Set the request timeout in MS
// 设置请求超时
request.timeout = config.timeout;
// 注意 promise的 resolve() 和 reject() 函数是在这里面执行的
function onloadend() {
//
if (!request) {
return;
}
// Prepare the response
// 准备 请求返回的结果 response
const responseHeaders = AxiosHeaders.from(
'getAllResponseHeaders' in request && request.getAllResponseHeaders()
);
const responseData = !responseType || responseType === 'text' || responseType === 'json' ?
request.responseText : request.response; //在这里判断 responseType 是什么值,然后获取对应的 请求结果
// 把结果进行封装一下
const response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config,
request
};
// 当readystatus == 4时,才会执行
// settle的参数为 成功回调,失败回调,response
settle(function _resolve(value) {
resolve(value);
done();
}, function _reject(err) {
reject(err);
done();
}, response);
// Clean up request
request = null;
}
//
if ('onloadend' in request) {
// Use onloadend if available
// ajax 的 onloadend 会在请求结束时触发
request.onloadend = onloadend;
} else {
// Listen for ready state to emulate onloadend
// 如果没有定义 ajax的onloadend的话,就在下面 监听 onreadystatechange,status 如果结果成功(状态时4)的话,就异步执行onloadend
request.onreadystatechange = function handleLoad() {
if (!request || request.readyState !== 4) {
return;
}
// The request errored out and we didn't get a response, this will be
// handled by onerror instead
// With one exception: request that using file: protocol, most browsers
// will return status as 0 even though it's a successful request
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);
};
}
// 下面都是定义在不同状态下的 ajax 的处理情况 例如 调用 ajax 的onabort,onerror,ontimeout等等
// Handle browser request cancellation (as opposed to a manual cancellation)
request.onabort = function handleAbort() {
if (!request) {
return;
}
reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request));
// Clean up request
request = null;
};
// Handle low level network errors
//设置出错时的情况
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;
};
// Handle timeout
// 设置超时的情况
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;
};
// 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 (platform.isStandardBrowserEnv) {
// Add xsrf header
const xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath))
&& config.xsrfCookieName && cookies.read(config.xsrfCookieName);
if (xsrfValue) {
requestHeaders.set(config.xsrfHeaderName, xsrfValue);
}
}
// Remove Content-Type if data is undefined
requestData === undefined && requestHeaders.setContentType(null);
// Add headers to the request
if ('setRequestHeader' in request) {
utils.forEach(requestHeaders.toJSON(), function setRequestHeader(val, key) {
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 (responseType && responseType !== 'json') {
request.responseType = config.responseType;
}
// Handle progress if needed
if (typeof config.onDownloadProgress === 'function') {
request.addEventListener('progress', progressEventReducer(config.onDownloadProgress, true));
}
// Not all browsers support upload events
if (typeof config.onUploadProgress === 'function' && request.upload) {
request.upload.addEventListener('progress', progressEventReducer(config.onUploadProgress));
}
if (config.cancelToken || config.signal) {
// Handle cancellation
// eslint-disable-next-line func-names
onCanceled = cancel => {
if (!request) {
return;
}
reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
request.abort();
request = null;
};
config.cancelToken && config.cancelToken.subscribe(onCanceled);
if (config.signal) {
config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
}
}
const protocol = parseProtocol(fullPath);
if (protocol && platform.protocols.indexOf(protocol) === -1) {
reject(new AxiosError('Unsupported protocol ' + protocol + ':', AxiosError.ERR_BAD_REQUEST, config));
return;
}
// Send the request
// 在最后,发起请求,
request.send(requestData || null);
});
其中设置了很多 ajax 的处理函数,最主要的内容是 onloadend函数 的定义。还有ajax请求发起的几个重要过程。在上面的代码中,已经把需要注意的地方用注释说明了,(建议放到编译器中方便看)
后续还有很多内容需要补充,待更新…