fetch号称是ajax的替代品,它的API是基于Promise设计的,旧版本的浏览器不支持 Promise。
关于fetch的用法 ,本文就不做介绍了,可以参看官方文档,可以得到很详细的介绍 使用Fetch - Web API 接口参考 | MDN
Fetch的代码结构比起ajax简单多了,参数有点像jQuery ajax。但是,一定记住fetch不是ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象。
fetch的优点:
- 符合关注分离,没有将输入、输出和用事件来跟踪的状态混杂在一个对象里
- 更好更方便的写法,坦白说,上面的理由对我来说完全没有什么说服力,因为不管是Jquery还是Axios都已经帮我们把xhr封装的足够好,使用起来也足够方便,为什么我们还要花费大力气去学习fetch?
我认为fetch的优势就是
- 语法简洁,更加语义化
- 基于标准 Promise 实现,支持 async/await
- 同构方便,使用 isomorphic-fetch
- 更加底层,提供的API丰富(request, response)
- 脱离了XHR,是ES规范里新的实现方式,是js原生方法不需要引入额外的库。
近在使用fetch的时候,也遇到了不少的问题:fetch是一个低层次的API,你可以把它考虑成原生的XHR,所以使用起来并不是那么舒服,需要进行封装。
例如:
- fetch只对网络请求报错,对400,500都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。
- fetch默认不会带cookie,需要添加配置项: fetch(url, {credentials: 'include'})
- fetch不支持abort,不支持超时控制。
- fetch没有办法原生监测请求的进度,而XHR可以
正对上面几个问题,我们下面分别来分析,并对其问题进行处理:
1. fetch请求对某些错误http状态不会reject
这主要是由fetch返回promise导致的,因为fetch返回的promise在某些错误的http状态下如400、500等不会reject,相反它会被resolve;只有网络错误会导致请求不能完成时,fetch 才会被 reject;所以一般会对fetch请求做一层封装。 简单的理解就是 fetch 只会认为断网这种情况才会是错误的,其他情况比如:404,403等请求错误都是认为请求成功了,应为它发起请求并收到了响应。
所以我们对返回状态进行校验,然后抛出错误,以便返回正常的报错信息。
对fetch中的错误进行抛出,然后对不同的状态返回不同的报错信息
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const errortext = response.statusText;
const error = new Error(errortext);
error.name = response.status;
error.response = response;
throw error;
}
let fetchChain = fetch(newUrl, newOptions)
.then(checkStatus)
.then(response => {
if (response.status === 204) {
return {};
}
if (newOptions.responseType === 'blob') {
return response.blob();
}
return newOptions.responseType === 'text' ? response.text() : response.json();
});
上面的代码中的checkStatus 主要是检查成功状态下的问题处理。其他情况不做处理。
如果要对其他的状态进行检查 需要通过catch来进行异常的捕获
。。。
fetchChain = fetchChain.catch(e => {
const status = e.name;
// 监听到 401 错误 重新登陆
if (status === 401) {
// code
}
// 监听到 网络请求错误
// https://github.com/github/fetch/issues/201
if (status === 'TypeError') {
// code
}
if (status === 501) {
// 后端正常的报错/服务器报错
}
if (status === 403) {
notification.error({
message: `${status}`,
description: '抱歉,您无权限访问此功能',
});
return;
}
// 其他错误
notification.error({
message: `${status}`,
description: e.message,
});
});
上面代码就实现了 fetch对某些错误http状态不会reject,同时面对不同状态的不同处理。
关于catch理解可以看对Promise中的resolve,reject,catch理解
2. fetch不支持abort,不支持超时控制。
fetch不像大多数ajax库那样对请求设置超时timeout,它没有有关请求超时的feature,这一点比较蛋疼。所以在fetch标准添加超时feature之前,都需要polyfill该特性。
实际上,我们真正需要的是abort(), timeout可以通过timeout+abort方式来实现,起到真正超时丢弃当前的请求。
而在目前的fetch指导规范中,fetch并不是一个具体实例,而只是一个方法;其返回的promise实例根据Promise指导规范标准是不能abort的,也不能手动改变promise实例的状态,只能由内部来根据请求结果来改变promise的状态。
既然不能手动控制fetch方法执行后返回的promise实例状态,那么是不是可以创建一个可以手动控制状态的新Promise实例呢。所以:
实现fetch的timeout功能,其思想就是新创建一个可以手动控制promise状态的实例,根据不同情况来对新promise实例进行resolve或者reject,从而达到实现timeout的功能;
方法一:单纯setTimeout方式
var oldFetchfn = fetch; //拦截原始的fetch方法
window.fetch = function(input, opts) {
//定义新的fetch方法,封装原有的fetch方法
return new Promise(function(resolve, reject) {
var timeoutId = setTimeout(function() {
reject(new Error("fetch timeout"));
}, opts.timeout);
oldFetchfn(input, opts).then(
res => {
clearTimeout(timeoutId);
resolve(res);
},
err => {
clearTimeout(timeoutId);
reject(err);
}
);
});
};
当然在上面基础上可以模拟类似XHR的abort功能:
var oldFetchfn = fetch;
window.fetch = function(input, opts) {
return new Promise(function(resolve, reject) {
var abort_promise = function() {
reject(new Error("fetch abort"));
};
var p = oldFetchfn(input, opts).then(resolve, reject);
p.abort = abort_promise;
return p;
});
};
方法二:利用Promise.race方法
Promise.race方法接受一个promise实例数组参数,表示多个promise实例中任何一个最先改变状态,那么race方法返回的promise实例状态就跟着改变,具体可以参考这里。
var oldFetchfn = fetch; //拦截原始的fetch方法
window.fetch = function(input, opts){//定义新的fetch方法,封装原有的fetch方法
var fetchPromise = oldFetchfn(input, opts);
var timeoutPromise = new Promise(function(resolve, reject){
setTimeout(()=>{
reject(new Error("fetch timeout"))
}, opts.timeout)
});
retrun Promise.race([fetchPromise, timeoutPromise])
}
通过上面两种方式发现可以发现:
timeout不是请求连接超时的含义,它表示请求的response时间,包括请求的连接、服务器处理及服务器响应回来的时间;
fetch的timeout即使超时发生了,本次请求也不会被abort丢弃掉,它在后台仍然会发送到服务器端,只是本次请求的响应内容被丢弃而已; 这样就会造成了流量的浪费。 关于怎么正在取消请求可以参考:https://github.com/hjylewis/trashable