前端领域如何实现请求中断( 二 )

以上示例对XHR请求操作流程进行了一下简单的封装,并未涉及到太多的细节和兼容处理 。一个简单的调用方式如下:
复制request({url: 'http://www.some-domain.com/path/to/example',method: 'POST',requestHeader: {'Content-Type': 'application/json; charset=UTF-8'},body: {key: value}}).then(response => console.log(response));1.2.3.4.5.6.基于以上操作便完成了一次客户端和服务器的数据交互请求,接下来在此基础上继续完善请求中断的相关逻辑 。
我们知道,在XHR实例上为我们提供了一个abort方法用于终止该请求,并且当一个请求被终止的时候,该请求所对应的XHR实例的readyState属性将会被设置为XMLHttpRequest.UNSET(0),同时status属性会被重置为0,因此在本示例中我们同样使用abort方法来实现请求中断 。
复制// 参考以上示例function request// 省略入参...} = {}) {return new Promise((resolve, reject) => {// 省略代码...});}// 存储请求接口地址以及请求体和 XHR 实例的映射关系request.cache = {};/** * @description: 根据提供的键名中断对应的请求* @param {String} key 存储在 request.cache 属性中的键名,若未提供则中断全部请求* @return {void} */request.clearCache = (key) => {if (key) {const instance = request.cache[key];if (instance) {instance.abort();delete request.cache[key];}return;}Object.keys(request.cache).forEach(cacheKey => {const instance = request.cache[cacheKey];instance.abort();delete request.cache[cacheKey];});};1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.在以上示例中,我们通过request.cache来临时存储请求接口地址以及请求体和XHR实例的映射关系,因为在同一页面中一般可能会涉及到多个接口地址不同的请求,或者同一个请求对应不同的请求体,因此这里考虑加上了请求体以做区分 。当然为了作为request.cache中的唯一键名,我们还需要对请求体进行序列化操作,因此简单封装一个序列化工具函数 。
复制/** * @description: 将请求体序列化为字符串 * @param {Document | XMLHttpRequestBodyInit | null} data 请求体 * @return {String} 序列化后的字符串 */request.serialize = (data) => {if (data && typeof data =https://www.isolves.com/it/cxkf/qd/2022-01-29/== 'object') {const result = [];Object.keys(data).forEach(key => {result.push(`${key}=${JSON.stringify(data[key])}`);});return result.join('&');}return data;}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.完成以上的基础代码之后,接下来我们将其应用到request函数中:
复制function request({url,body = null,// 省略部分入参...} = {}) {return new Promise((resolve, reject) => {if (!url) {return reject(new TypeError('the required parameter [url] is missing.'));}// 省略部分代码...const xhr = new XMLHttpRequest();// 将请求接口地址以及请求体和 XHR 实例存入 cache 中let cacheKey = url;if (body) {cacheKey += `_${request.serialize(body)}`;}// 每次发送请求之前将上一个未完成的相同请求进行中断request.cache[cacheKey] && request.clearCache(cacheKey);request.cache[cacheKey] = xhr;// 省略部分代码...xhr.onreadystatechange = function onReadyStateChange() {if (xhr.readyState === XMLHttpRequest.DONE) {if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {// 请求完成之后清除缓存request.clearCache(cacheKey);resolve(xhr.response);}}};xhr.onerror = function onError(error) {console.log(error);// 请求报错之后清除缓存request.clearCache(cacheKey);reject({ message: '请求出错,请稍后重试' });};xhr.ontimeout = function onTimeout() {// 请求超时之后清除缓存request.clearCache(cacheKey);reject({ message: '接口超时,请稍后重试' });};xhr.send(body ? JSON.stringify(body) : null);});}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.这样便简单实现了一个自包含的请求中断的处理逻辑,每次发送请求之前自动判定未完成的多余请求并将其清除,从而避免性能上的开销 。
当然,不仅如此,这里同样可以通过request.clearCache函数来在组件卸载或路由跳转的时候手动清除未完成的请求,因为这部分请求对于卸载后的组件而言没有太多实质意义,例如以下示例:
复制// 网页卸载前清除缓存window.addEventListener('beforeunload', () => request.clearCache(), false);// Vue 中路由跳转前清除缓存router.beforeEach((to, from, next) => { request.clearCache(); next(); });// React 中路由跳转时清除缓存import { Component } from 'react';import { withRouter } from 'react-router-dom';class App extends Component {componentDidMount() {// 监听路由变化this.props.history.listen(location => {// 通过比较 location.pathname 来判定路由是否发生变化if (this.props.location.pathname !== location.pathname) {// 若路由发生变化,则清除缓存request.clearCache();}});}}export default withRouter(App);1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.


推荐阅读