fetch、axios、ajax - 有关中止请求
参考文章:ajax和axios、fetch的区别、MDN - AbortController、MDN - AbortSignal
背景:
有一个查询接口,响应时间很长(5min)左右,所以需要提供一个用户不愿意等待时的「中止」按钮
知识学习:
一开始我们用的 XHRHttpRequest【传统 ajax】
-> jQuery 封装原生 XHR + 增添了对JSONP的支持,我们开始使用【JQuery ajax】
缺点:
1.本身是针对MVC的编程,不符合现在前端MVVM的浪潮 2.基于原生的XHR开发,XHR本身的架构不清晰。 3.JQuery整个项目太大,单纯使用ajax却要引入整个JQuery非常的不合理(采取个性化打包的方案又不能享受CDN服务) 4.不符合关注分离(Separation of Concerns)的原则 5.配置和调用方式非常混乱,而且基于事件的异步模型不友好
-> 本质上也是对原生XHR的封装,但是是 Promise 实现的版本,基于Promise 用于浏览器和 nodejs 的 HTTP 客户端【axios】
特性:
1.从浏览器中创建 XMLHttpRequest 2.支持 Promise API 3.客户端支持防止CSRF 4.提供了一些并发请求的接口(重要,方便了很多的操作) 5.从 node.js 创建 http 请求 6.拦截请求和响应 7.转换请求和响应数据 8.取消请求 9.自动转换JSON数据
-> fetch 是基于 Promise 设计的,但是不是 ajax 的进一步封装,是用原生 js 实现的,没有用 XHR 对象【fetch】
优点:
- 语法简洁,更加语义化
- 基于标准 Promise 实现,支持 async/await
- 同构方便,使用 isomorphic-fetch
- 更加底层,提供的API丰富(request, response)
- 脱离了XHR,是ES规范里新的实现方式
缺点:
1)fetch只对网络请求报错,对400,500都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。
2)fetch默认不会带cookie,需要添加配置项: fetch(url, {credentials: ‘include’})
3)fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费
4)fetch没有办法原生监测请求的进度,而XHR可以
针对 fetch 使用的封装:
async function fetchBase(apiurl, options) {
let url = apiurl;
let opt = options;
opt = opt || {};
opt.method = opt.method || 'GET';
opt.credentials = opt.credentials || 'include';
opt.withCredentials = true;
if (opt.headers) {
Object.assign(opt.headers, {
Accept: 'application/json'
});
}
if (opt.query) {
// 语法:querystring.stringify(obj, [sep=‘&’], [eq=‘=’])
// 作用:将对象转换成字符串,默认用 & 连接多个参数,用 = 连接键值
// 例如:将{ foo: 'bar', baz: ['qux', 'quux'], corge: '' } 转换为-->'foo=bar&baz=qux&baz=quux&corge='
url = `${url}?${queryString.stringify(opt.query)}`;
}
// 防止同步render报错
setImmediate(() => Progress.start());
const res = await fetch(url, opt);
try {
let json = { code: 200 };
if (opt.raw) {
Progress.done();
return res.text();
}
// 如果是HEAD请求,说明不需要返回数据
if (!opt.method.toUpperCase() !== 'HEAD') {
json = await parseJSON(res);
}
if (json.code === 200 || json.resultCode === 200 || json.resultCode === 0) {
Progress.done();
return {
...json,
// 有些场合数据是在HEADER中的
_headers: res.headers
};
}
// if (json.code === 401 || json.code === 301) {
// Progress.done();
// // 未登陆的情况下,跳转登陆页, 这一步在Layout中处理
// // window.location.href = '/login';
// return false;
// }
throw json;
} catch (err) {
Progress.done();
if (err.code === 302 || err.resultCode === 302) {
// 状态码为 302 时跳转至登录页
message.error('用户未登录或登录状态失效,请登录后再试');
setTimeout(() => {
// encodeURIComponent用于编码字符为UTF-8的编码,比encodeURI编码更多字符,例如?=&
window.location.href = `${api.login}?redirect=${encodeURIComponent(window.location.href)}`;
}, 1000);
} else if (err.code === 301 || err.resultCode === 301) {
// 状态码为 301 时提示无权限
message.error('无操作权限,请联系系统管理员');
} else if (!opt.noCommonTip && err.name !== 'AbortError') {
// 其他错误情况,统一toast提示后端msg 信息
const errorMsg = err.message || err.msg || err.errMsg || defaultErrorMessage;
message.error(errorMsg);
}
throw err;
}
// return false 可以在对应model通过if(data)来判断是否取到值
// return false;
}
针对可以中止的 fetch 的封装:
const fetchAbortWrapper = (url, options) => {
const aborter = new AbortController();
const req = fetchBase(url, {
...options,
signal: options.signal || aborter.signal,
});
req.abort = () => aborter.abort();
return req;
};