AboutFE
AboutFE copied to clipboard
7、请求相关
fetch
之前在项目中用到了这个fetch 来代替Ajax 进行网络请求。也踩了不少的坑,在这边列举出来以及他的解决方法。
如何保持每次请求的会话一致
在用fetch进行网络请求的时候,发现每次请求到服务端的时候,他的sessionId 都是不一样的,后面排查原来是在请求的时候fetch默认是不会带上本地jsessionId,以至于服务端无法接收到,所以会重新创建一个新的session。
解决办法:
var init = {
credentials: 'include' // 请求带上cookies,是每次请求保持会话一直
...
}
兼容性,支持IE10+、谷歌、火狐等
由于fetch是一个新技术,有些旧的浏览器对它并不支持,这时候要怎么兼容?
解决办法:
引入一个额外的补丁es6-promise.js可以使它很好的支持IE9以上的版本,那IE8呢?IE8 需要改fetch.js源码才能支持 fetch.js 只需改两处:
...
try{
Object.getOwnPropertyNames(headers).forEach(function(name) {
this.append(name, headers[name])
}, this)
}catch(e){
var a = [];
for (var i in headers) {
if (headers.hasOwnProperty(i)) {
a.push(i); // 输出 foo asj
}
};
a.forEach(function(name) {
this.append(name, headers[name])
}, this)
}
}
Headers.prototype.forEach = function(callback, thisArg) {
try{
Object.getOwnPropertyNames(this.map).forEach(function(name) {
this.map[name].forEach(function(value) {
callback.call(thisArg, value, name, this)
}, this)
}, this)
}catch(e){
var a = [];
for (var i in this.map) {
if (this.map.hasOwnProperty(i)) {
a.push(i); // 输出 foo asj
}
};
a.forEach(function(name) {
this.map[name].forEach(function(value) {
callback.call(thisArg, value, name, this)
}, this)
}, this)
}
}
...
try里面的是源码本身的,catch里面的是小编额外加的。细心的会发现这两处改的是一样的,都是对getOwnPropertyNames进行替换。但是IE8也不支持forEach为啥没有替换 呢? 其实有替换了,只是不再这边体现。我把它写进了Array.prototype里面了,其实fetch.js里面也用到了indexOf,它在IE8下也是不支持,我把它一并的放在Array.prototype;如下:
Array.prototype.forEach = function(callback, thisArg) {
var T, k;
if (this == null) {
throw new TypeError(" this is null or not defined");
}
var O = Object(this);
var len = O.length >>> 0; // Hack to convert O.length to a UInt32
if ({}.toString.call(callback) != "[object Function]") {
throw new TypeError(callback + " is not a function");
}
if (thisArg) {
T = thisArg;
}
k = 0;
while (k < len) {
var kValue;
if (k in O) {
kValue = O[k];
callback.call(T, kValue, k, O);
}
k++;
}
};
Array.prototype.indexOf = function(elt /*, from*/){
var len = this.length >>> 0;
var from = Number(arguments[1]) || 0;
from = (from < 0)
? Math.ceil(from)
: Math.floor(from);
if (from < 0)
from += len;
for (; from < len; from++)
{
if (from in this &&
this[from] === elt)
return from;
}
return -1;
};
Axios使用及源码分析
背景
工程院推进的React脚手架使用Axios网络库,Vue2.0也推荐使用。结合平常开发过程中的使用,总结遇到的问题以及分析部分核心源码。
Axios介绍
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中
它有以下特性:
- 支持Promise API
- 支持request和response拦截
- 转换请求与响应数据
- 取消请求
- 支持客户端XSRF攻击防护
- 支持各主流浏览器及IE8
Axios基本使用
一般使用请求如下,符合Restful风格
axios.request(config)
axios.get(url, config)
axios.delete(url, config)
axios.head(url, config)
axios.post(url, data, config)
axios.put(url, data, config)
axios.patch(url, data, config)
config配置参数可自定义:
config = {
baseURL: 'http://esp-homework-api', //请求域名
headers: XXX, //请求头,可添加自定义参数
ignoreErrorCodes: ['XXX','XXX'], // 全局统一拦截时,如果配置该参数,则不会走错误拦截处理
methods: 'POST', //没设置methods时,默认是GET请求
timeout: 500
}
- Axios并发请求
function getUser1() {
return axios.get('/user/818118');
}
function getUser2() {
return axios.get('/user/910604');
}
axios.all([getUser1(), getUser2()])
.then(axios.spread(function (data1, data2) {
// 两个请求都完成时,处理逻辑,data1、data2分别代表两个请求返回的结果
}));
Axios实际上是一个Promise,所以上述请求等同于下面的写法:
Promise.all([getUser1(), getUser2()]).then(([data1, data2])=> {
两个请求都完成时,处理逻辑
})
- Axios请求返回的内容
{
data:{}, //真实返回数据,前端需要处理的
status:200,
statusText:'OK',
headers: {},
config: {}
}
可以测试下请求返回数据
axios.get('/user/818118')
.then(function(res){
console.log(res.data);
console.log(res.status);
console.log(res.statusText);
console.log(res.headers);
console.log(res.config);
})
- 拦截器
网络请求发出去之前进行拦截:
axios.interceptors.request.use(function(config){
return config;
},function(err){
return Promise.reject(error);
});
实际使用场景,可以设置auth参数:
axios.interceptors.request.use(function(config=> {
config.headers['Authorization'] = 'xxx'
})
网络请求返回之前进行拦截:
axios.interceptors.response.use(function(res){
return res;
},function(err){
return Promise.reject(error);
});
实际使用场景,因为axios实际返回是一个结构体,包含data数据,所以我们可以直接拦截返回给前台调用:
axios.interceptors.response.use(function(response=> {
return response.data
})
源码分析
主要分析拦截器的相关源码,在这之前,我们有必要回顾下Promise的知识
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function funcA(comments) {
console.log("resolved: ", comments);
}, function funcB(err){
console.log("rejected: ", err);
});
第一个then方法指定的回调函数,返回的是另一个Promise对象。这时,第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。如果变为resolved,就调用funcA,如果状态变为rejected,就调用funcB
整个拦截的过程,大概如下:
interceptors.request -> request -> interceptors.response -> response
我们现在来看下Axios类的定义
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
结合上面的使用方式axios.interceptors.request.use,interceptors主要是由一个InterceptorManager类的实例实现。
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};
InterceptorManager的功能简单地讲,只是通过一个数组来维护拦截器,每个拦截器的两个参数分别作为Promise中的resolve和reject
InterceptorManager的使用是在Axios类中
Axios.prototype.request = function request(config) {
// 挂载interceptor中间件
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;
};
当执行request请求时,会遍历之前定义的interceptors拦截器,通过一个数组chain维护,经过遍历逻辑后,chain变量变为:
[interceptor.request.fulfilled, interceptor.request.rejected,
dispatchRequest, undefined,
interceptor.response.fulfilled, interceptor.response.rejected]
while方法循环包装成一个promise返回:
Promise.resolve(config)
.then(interceptor.request.fulfilled, interceptor.request.rejected)
.then(dispatchRequest, undefined)
.then(interceptor.response.fulfilled, interceptor.response.rejected)
这样就是之前我们说的promise嵌套使用。
- 遇到问题
开发过程中,有使用过实例化axios的方法
var instance = axios.create({
baseURL:"https://some-domain.com/api/"});
但是新实例化的对象不会执行之前定义的拦截器方法,我们分下下创建实例的源码
function createInstance(defaultConfig) {
var context = new Axios(defaultConfig);
var instance = bind(Axios.prototype.request, context);
utils.extend(instance, Axios.prototype, context);
utils.extend(instance, context);
return instance;
}
// Factory for creating new instances
axios.create = function create(instanceConfig) {
return createInstance(utils.merge(defaults, instanceConfig));
};
因为重新实例化后,不会将原先定义的参数同步过来,需要自己重新配置
总结
Axios的有点在于既能在web,又能在node中使用,而且轻量级。我们在阅读源码时,可以学习它的封装思路,有助于在工程中实践。