前言
最近都再看算法的内容,突然发现自己axios方面的知识还没有学习好,也攒下了不少偏笔记,在这里浅浅记录一下自己学习的历程。
当我们npm install axios
后,打开axios的 package.json
可以找得到axios的入口文件 "main": "index.js"
我们顺着这个文件去一步步理清axios的内容
随之我们进入index.js
的文件中
引入部分
文件最上面的部分是各种文件的引入,包括配默认置文件,工具函数,Axios主文件
下面的部分中可以看得出来,axios是一个函数。
是生成实例对象
axios
、axios.Axios
、axios.create
等。
函数的内容如图
Axios实例
bind函数
创建instance
,返回一个新的 wrap 函数,extend函数
将Axios原型和实例上的内容绑定到instance
上,
axios.defaults
和拦截器 axios.interceptors
可以使用的原因axios.create()
时执行的内容剩下的文件中的部分就是为 axios函数
添加方法
取消相关API实现,还有
all
、spread
、导出等实现。
这些还没有去研究,之后再写吧
源码中有一些自定义的工具函数,和平时我们认知的有一点不一样,把他们单独拿出来讲一下
这里重写的bind函数并不像我们平时的bind函数一样,参数的位置有一丢丢不一样,但也容易理解
我们再utils
文件中找到extend
项
加了大量的注释,应该一下子就能看懂了,
调用forEach时
forEach(遍历的内容,处理的函数(值,索引,遍历的内容){处理细节}, 是否全是自身的key)
看完这三个 方法工具 上面的
createInstance函数
应该就能看懂了
把多个对象的值都放到一起来。
在上面的axios.js
的文件中
这一步是非常关键的,其中出现了很多次的 Axios
。同时这也是 axios的关键所在
Axios构造函数
Axios文件中主要内容在这个Axios构造函数的构建。
里面包括了
axios('url',config)
,axios({config})
的配置方式get,post,delete
等方法绑定到实例的原型上这些内容,如果把这些内容掌握了axios就差不多了
无论是直接通过axios('url',{config})
的方法调用 axios 还是 axios({config})
或者是 axios.get()
的方式使用axios , 都绕不过config的配置
使用这两种方式时,原理上就是直接调用Axios的request方法
这时会对调用时,传入的参数进行判断,
如果第一个参数是字符串形式 即用 axios("url",{})
的方法,就会将第一个参数中的 url 复制到 第二个参数的config中
如果第一个字符串不存在 即用 axios({})
的方式,就将第一个参数的值,直接给了config,
之后还要将传入配置和axios默认配置进行合并。
axios.get()
调用Axios的原型中绑定了一些列的请求方法,调用这些方法会返回一个 this.request同样也是执行请求
,会将调用时传入的一系列参数都合并起来,组成一个 config对象
mergeConfig中有争对不同的配置型,确定合并的方式,有一个Map的映射表,还有处理的forEach
将所有的配置文件(用户传入的,默认配置的等等)进行一个合并,最终返回合并后的结果
在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.js
dispatchRequest中主要是对 配置data 和 返回的data的处理,发送请求时在 adapter 里面进一步划分的,
值得注意的内容是 config.transformRequest
中一系列函数 对data content-type的处理
在 defaults/index.js中我们可以找到 adapter的内容
我们顺着这个线索一步步往下
同样在 defaults/index.js 中
这里就是通过两个if
语句来判断当前的环境是 浏览器环境还是node环境 。通常都是使用 xhr 在浏览器端发送请求
adapters/index.js
最后让我们进入到 xhr 的请求中看一看具体的发送请求过程
这里是我们真正发起请求的地方
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请求发起的几个重要过程。在上面的代码中,已经把需要注意的地方用注释说明了,(建议放到编译器中方便看)
后续还有很多内容需要补充,待更新…