一、流程图
1.1 发送请求的方法
axios(config)
axios.request(config)
axios.get(url,config)
axios.head(url,config)
axios.delete(url,config)
axios.options(url,config)
axios.post(url,data,config)
axios.put(url,data,config)
axios.patch(url,data,config)
第一个方法是语法糖具体参考https://www.jianshu.com/p/3a2404ef5566。第二个方法request是axios真正发送请求的方法。后面这些方法只是对request方法的一个封装。他们之间除了method不同之外还有data的区别。其中get、head、delete、options是不会发送data的,这意味着即使data有值,使用这些方法发送请求也是不会包含进去的。
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url
}));
};
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, data, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url,
data: data
}));
};
});
1.2 request函数
request函数可以说是包含了整个请求的全部流程,但是更细节的部分还得看各个函数究竟做了什么。目前只从一个大的角度看整体流程走向。
整个请求执行过程分为三部分:
- request拦截器
- 请求分发
- response拦截器
1.3 拦截器
它的定义在Axios的构造函数中,是2个InterceptorManager的实例。
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
InterceptorManager 从本质上讲就是一个事件管理器,基于发布-订阅模式设计的。这不是重点,说简单点就是我们事先通过以下语法向其中添加一些想在请求执行前和请求执行后要做的事情(函数)。在每次request的时候就会把它放在对应的位置执行它。
axios.interceptors.request.use(function resolveFunction(){
},function rejectFunction(){
})
1.4 request函数各个执行过程的组织方式
var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
首先有核心的请求函数dispatchRequest,这里初始化的时候是两个元素,这是因为异常处理逻辑和正常处理逻辑都放到一个数组里面了,而且后面promise取出参数的时候一次取出的是两个。最关键的是undefined不影响数据传递。
request拦截器中的处理函数是放在dispatchRequest前面,response拦截器是放在dispatchRequest后面。unshif是每次都放在最前面,因此往request拦截器最后添加的函数是最先执行的。
这些函数都被放到promise链中执行,在中间进行数据传递。因此在axios中添加拦截器时一定要注意返回一个结果,将数据传递下去。
1.5 promise链数据传递
- 第一种情况是正常情况,按着正常处理的链进行
- 第二种情况是第一步执行报错了,但是在下一个promise的异常处理函数中被捕获到了。这个时候可以选择处理,然后返回正常数据传递给下一个promise的正常处理函数,也可以抛出异常交给下一个promise的异常函数去处理。
- 第三种情况第一步执行出错了,但是下一个promise没有异常处理函数,也就是为undefined,这时错误会继续向后传递,直到被某一个promise的异常处理函数捕获。
1.6 请求分发
在这个部分中,axios主要任务是发送http请求,config传到dispatchRequest函数中,这个时候config中给的data和headers不一定是http请求直接可以拿来用的。因此它先对data和headers进行了处理:
-
config.headers = config.headers || {};
防止headers没有传递的情况,初始化 - 根据
config.transformRequest
的函数对其变化,默认的处理是- 对headers的【Accept、Content-Type】两个属性大小写的兼容,因为ajax请求对headers大小写是有严格要求的。
- 对特定数据进行检查,一般发送的data是字符串,但也不乏文件、二进制、流数据等,如果是FormData、ArrayBuffer、Buffer、Stream、File、Blob就不用处理。
- 对ArrayBufferView取其中的buffer(这个数据类型没见过)
- 如果是URLSearchParams 还需要设置
content-type
为application/x-www-form-urlencoded;charset=utf-8
然后再将其转换成字符串 - 发送普通的object,普通的object需要转换成字符串。Object转换需要用json序列化
- 将config中所有包含headers的位置合并,有
config.headers.common
,config.headers[method]
,config.headers
,并删除headers中多于的属性 - 获取发送ajax请求的adapter(因为axios还提供了nodejs的版本,所以这里他会判断环境而选择适合的adapter),并发送请求(细节见axios封装ajax请求)
- 对于ajax请求的结果进行处理,处理过程是放在请求promise的then方法中,具体处理是如果结果是字符串类型,尝试用JSON将其转换成对象。如果转换失败,返回原始数据,否则返回转换好之后的数据。
- 对请求promise的异常进行处理,处理方式如下:
- 判断异常类型如果是【取消请求】(指包含属性__CANCEL__,且其值为true),那么将这个异常传递下去
Promise.reject(reason)
- 如果是其他异常使用config.cancelToken这个属性尝试抛出一个【取消请求】的异常(如果没有手动调用取消请求的函数,它会什么也不做,否则才会抛出一个异常)。需要手动抛出取消请求的异常是因为取消请求时是由onabort抛出的ECONNABORTED异常, 但是此时在config.cancelToken中做了标记,所以每当有异常出现会手动尝试抛出一下异常
- 如果进行到这一步,说明不是【取消请求】的异常,这时对异常携带的data做出处理。
- 判断异常类型如果是【取消请求】(指包含属性__CANCEL__,且其值为true),那么将这个异常传递下去
二、手写一个简单的promise链,将函数顺序执行
function Chain(data){
if(!(this instanceof Chain)){
return new Chain(data)
}
this.chain = []
this.data = data
}
//尾部添加
Chain.prototype.use = function use(resolve_handler,reject_handler){
this.chain.push(resolve_handler,reject_handler)
return this
}
//执行
Chain.prototype.exec = function exec(){
var promise = Promise.resolve(this.data)
var chain = this.chain
while(chain.length > 0){
promise = promise.then(chain.shift(),chain.shift())
}
return promise
}
function b1(data){
console.log("b1",data)
throw new Error("b1 error")
return data + 1
}
function b2(err){
console.log("err")
// return 100
}
function kernel(data){
console.log("kernel",data)
return data + 1
}
function a1(data){
console.log("a1",data)
return data + 1
}
Chain(0).use(b1).use(kernel).use(a1).exec().then(function(data,e){
console.log(data)
}).catch(function h(err){
console.log(err)
})
三、完整Axios定义代码
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
Axios.prototype.request = function request(config) {
/*eslint no-param-reassign:0*/
// Allow for axios('example/url'[, config]) a la fetch API
if (typeof config === 'string') {
config = arguments[1] || {};
config.url = arguments[0];
} else {
config = config || {};
}
config = mergeConfig(this.defaults, config);
// Set config.method
if (config.method) {
config.method = config.method.toLowerCase();
} else if (this.defaults.method) {
config.method = this.defaults.method.toLowerCase();
} else {
config.method = 'get';
}
// Hook up interceptors middleware
var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
Axios.prototype.getUri = function getUri(config) {
config = mergeConfig(this.defaults, config);
return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, '');
};
// Provide aliases for supported request methods
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url
}));
};
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, data, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url,
data: data
}));
};
});