axios 中的发送请求
前面讲解了
mergeConfig和拦截器等方法,今天了解发送请求dispatchRequest这个核心方法
dispatchRequest
请求拦截器,和响应拦截器,都是一个数组, var chain = [dispatchRequest, undefined]; axios 通过 Promise 链条把请求拦截器数组,chain,响应拦截器数组组成一个整体,其中dispatchRequest处于链条的中央,是核心方法
var dispatchRequest = function dispatchRequest(config) {
var adapter = config.adapter || defaults_1.adapter;
return adapter(config).then(
function onAdapterResolution(response){
response.data = transformData.call(
config,
response.data,
response.headers,
response.status,
config.transformResponse
);
return response;
},function onAdapterRejection(reason){
if (!isCancel(reason)) {
// Transform response data
if (reason && reason.response) {
reason.response.data = transformData.call(
config,
reason.response.data,
reason.response.headers,
reason.response.status,
config.transformResponse
);
}
}
return Promise.reject(reason);
}
}
dispatchRequest接受传入config配置
如果config中adapter就使用传入的adapter,否则就用默认的adapter
一般情况下,都时用的默认配置,adapter为一个接收config,返回一个Promise函数
其中利用transformData方法对Promise的value 和 reason 进行再一步的处理,返回给下一个Promise链条
adapter进行多次封装,真正发送的方法是xhr
xhr
发送请求的方法,使用的 xhr 发送请求,没有使用fetch,返回一个Promise
var xhr = function xhrAdapter(config){
return new Promise(function dispatchXhrRequest(resolve, reject) {
var requestData = config.data;
var request = new XMLHttpRequest();
var fullPath = buildFullPath(config.baseURL, config.url);
request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);
request.timeout = config.timeout;
...
function onloadend() {
var response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config: config,
request: request
};
settle(function _resolve(value) {
resolve(value);
done();
}, function _reject(err) {
reject(err);
done();
}, response);
}
request.onloadend = onloadend;
...
request.send(requestData);
}
}
上面是核心方法,返回一个Promise
可以看出axios对url进行封装,第一次是使用buildFullPath把baseURL和url进行合并 ,第二次是发送请求的时候,使用buildURL,带上config.parmas,又进行了一次封装
先看方法buildFullPath,看如何合并URl
buildFullPath
把baseURL和url进行合并
/**
* Creates a new URL by combining the baseURL with the requestedURL,
* only when the requestedURL is not already an absolute URL.
* If the requestURL is absolute, this function returns the requestedURL untouched.
*
* @param {string} baseURL The base URL
* @param {string} requestedURL Absolute or relative URL to combine
* @returns {string} The combined full path
*/
var buildFullPath = function buildFullPath(baseURL, requestedURL) {
if (baseURL && !isAbsoluteURL(requestedURL)) {
return combineURLs(baseURL, requestedURL);
}
return requestedURL;
};
判断baseURL存在,并且 requestedURL不是绝对路径,执行combineURLs合并方法
否则直接返回requestedURL
判断是否是绝对路径也很有意思
var isAbsoluteURL = function isAbsoluteURL(url){
return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url);
}
使用正则判断,只要满足开头是(小写英文字母+((小写英文字母 | 数字 | - | . )0个或者多个) 一个或者多个) + //
形如aa://或者"a2.://" 都可以满足
然后执行combineURLs方法,它也很有意思
var combineURLs = function combineURLs(baseURL, relativeURL) {
return relativeURL
? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '')
: baseURL;
};
如果baseURL是以/结尾,就替换成'',如果是relativeURL是以/开头,就把/替换成''
经过这种变化,最终返回形如https://www.aaa/aaaa这种fullpath路径
buildURL
var buildURL = function buildURL(url, params, options) {
// hash标记下标
var hashmarkIndex = url.indexOf('#');
if (hashmarkIndex !== -1) {
url = url.slice(0, hashmarkIndex);
}
var _encode = options && options.encode || encode;
var serializerParams = utils.isURLSearchParams(params) ?
params.toString() :
new AxiosURLSearchParams_1(params, options).toString(_encode);
if (serializerParams) {
url += (url.indexOf('?') === -1 ? '?' : '&') + serializerParams;
}
return url;
};
接收三个参数url,params,options
- 获取纯净url
判断是否有
#,使用slice切割,只要url,不要后面的参数 - encode 参数(params)
判断
params是否是URLSearchParams格式,如果是,调用toString方法,URLSearchParams 无法解析完成的url
URLSearchParams
var paramsString2 = "?query=value";
var searchParams2 = new URLSearchParams(paramsString2);
searchParams2.has("query") //true
如果不是URLSearchParams格式,就自己写一个类似URLSearchParams的方法
function AxiosURLSearchParams(params, options) {
this._pairs = [];
// 对 params 进行处理,比如如果是Date 格式,要执行value.toISOString() 方法
// 如果是 null ,转化为 ''
params && toFormData_1(params, this, options);
}
var prototype = AxiosURLSearchParams.prototype;
prototype.append = function append(name, value) {
this._pairs.push([name, value]);
};
// 编码,否则无法解析
// function encode(val) {
// return encodeURIComponent(val).
// replace(/%3A/gi, ':').
// replace(/%24/g, '$').
// replace(/%2C/gi, ',').
// replace(/%20/g, '+').
// replace(/%5B/gi, '[').
// replace(/%5D/gi, ']');
// }
prototype.toString = function toString(encoder) {
var _encode = encoder ? function(value) {
return encoder.call(this, value, encode$1);
} : encode$1;
return this._pairs.map(function each(pair) {
return _encode(pair[0]) + '=' + _encode(pair[1]);
}, '').join('&');
};
最后加上上文中buildFullPath返回的完整的url
产出一个形如http://www?id=2&name=zs的这种完整请求格式
请求发送之后的几种情况
当请求发送出去之后,分成几种情况
- 成功
onloaded - 失败或者中断请求
onabort,onerror - 超时
ontimeout - 如果是 上传/下载,还需要有进度函数
progress
成功 onloaded
function onloadend(){
// 又一次封装
var response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config: config,
request: request
};
settle(function _resolve(value) {
resolve(value);
done();
}, function _reject(err) {
reject(err);
done();
}, response);
// Clean up request
request = null;
}
使用settle方法,接收两个函数和一个封装的response,执行函数,执行Promise的resolve方法或者reject方法,和 done方法
看看settle 方法
var settle = function settle(resolve, reject, response){
var validateStatus = response.config.validateStatus
if( validateStatus(response.status)){
resolve(response);
}else {
reject(new AxiosError_1(
'Request failed with status code ' + response.status,
[AxiosError_1.ERR_BAD_REQUEST, AxiosError_1.ERR_BAD_RESPONSE][Math.floor(response.status / 100) - 4],
response.config,
response.request,
response
))
}
}
如果 发送的请求返回后的状态码满足validateStatus,就立即调用resolve 方法,xhr方法Promise进入成功态,否则执行reject,进入失败态
validateStatus 比较简单,是从config配置或者使用默认
validateStatus: function validateStatus(status) {
return status >= 200 && status < 300;
},
失败或者中断请求onabort,onerror
都比较简单,只要发送错误信息即可
request.onabort = function handleAbort() {
if (!request) {
return;
}
reject(new AxiosError_1('Request aborted', AxiosError_1.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_1('Network Error', AxiosError_1.ERR_NETWORK, config, request));
// Clean up request
request = null;
};
特别注意,在浏览器中,如果状态码是500也属于发送成功,执行onLoad方法,只有网络请求失败,才执行onError方法
超时ontimeout
request.ontimeout = function handleTimeout(){
var timeoutErrorMessage = config.timeout ? 'timeout of ' + config.timeout + 'ms exceeded' : 'timeout exceeded';
reject(new AxiosError_1(
timeoutErrorMessage,
transitional$1.clarifyTimeoutError ? AxiosError_1.ETIMEDOUT : AxiosError_1.ECONNABORTED,
config,
request));
// Clean up request
request = null;
}
比较简单,直接reject出错误信息
进度回调progress
if (typeof config.onDownloadProgress === 'function') {
request.addEventListener('progress', config.onDownloadProgress);
}
// Not all browsers support upload events
if (typeof config.onUploadProgress === 'function' && request.upload) {
request.upload.addEventListener('progress', config.onUploadProgress);
}
如果配置了onDownloadProgress或者有onUploadProgress,就需要对用户报知当前进度
request.upload-MDN
只讨论了发送出去的情况,还有中断请求,也就是 cancel的情况,留到下一次再说
错误处理函数
其中在ontimeout和onerror方法中,多次使用 reject(new AxiosError_1(...)) ,
AxiosError_1 来探究一下
function AxiosError(message, code, config, request, response) {
Error.call(this);
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
} else {
this.stack = (new Error()).stack;
}
this.message = message;
this.name = 'AxiosError';
code && (this.code = code);
config && (this.config = config);
request && (this.request = request);
response && (this.response = response);
}
AxiosError 首先把调用 Error.call方法,改变this指向
Error.captureStackTrace(this, this.constructor);
由于Error.captureStackTrace()可以返回调用堆栈信息,因此在自定义Error类的内部经常会使用该函数,用以在error对象上添加合理的stack属性
error 简化版,例如:
function AxiosError(message) {
Error.call(this);
// 只有v8 的浏览器支持这种captureStackTrace
Error.captureStackTrace(this, this.constructor);
this.message = message;
}
let r2 =new AxiosError("zzz",500)
Promise.reject(r2)
如果有 Error.captureStackTrace(this, this.constructor);
如果没有
简单的call继承
function Person (name,age){
this.name = name || "person";
this.age = age
}
function Student(age,grade){
Person.call(this)
this.age = age;
this.grade = grade
}
let s = new Student(10,3)
console.log(s) // name:'person',age:10,grade:3
总结
从这个xhr方法中,学到了很多方法和技巧
- 判断http是否是绝对路径
- request 原生的 上传下载的进度函数
- 错误处理
- 使用 call 继承
Error类
time:2022/10/2,还有取消请求尚未完成,axios就要完结