在项目中可能遇到以下场景:
- 在
Vue
或React
单页应用中,组件A
挂载完毕之后向后台服务发起请求拉取数据,但是由于加载过慢,用户可能期间发生路由跳转或回退,导致组件A
卸载,但是组件内部的网络请求并没有立即停止下来,此时的响应数据对于已卸载的组件A
而言已经无效。若刚好此时请求响应错误,就可能导致前端实现的兜底弹窗出现在跳转后的页面中,造成视觉干扰;- 页面存在定时轮询业务,即固定间隔一段时间再次发起请求,这样就可能存在多个请求间的竞争关系,如果上一个请求的响应速度比最近一次请求的响应速度慢,则前者就会覆盖后者,从而导致数据错乱;
以上是工作项目中遇到的场景。然后查阅了相关的博客,还有要下相关的场景。
- 类似于关键字搜索或模糊查询等需要频繁发起网络请求的相关业务,可能在一定程度上为了优化程序的执行性能,减少冗余的网络
IO
,我们会使用防抖(debounce)函数
来对请求逻辑进行包装,减少查询次数以降低服务器压力,但是依旧避免不了由于加载耗时过长导致新老请求数据错乱的问题;- 针对前端大文件上传等上传服务,需要实现上传进度的
暂停
与恢复
,即断点续传
。
减少客户端和服务器之间的无效传输,是页面优化的一种方法。
浏览器原生实现的XMLHttpRequest(以下简称XHR)
构造函数:
在XHR
实例上为我们提供了一个abort
方法用于终止该请求,并且当一个请求被终止的时候,该请求所对应的XHR
实例的readyState
属性将会被设置为XMLHttpRequest.UNSET(0)
,同时status
属性会被重置为0。
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];
});
};
Axios:
Axios
提供的CancelToken
构造函数来取消请求。
const instance = axios.create({
baseURL: 'https://www.some-domain.com/path/to/example',
timeout: 30000,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
// 设置 axios 实例默认配置
instance.defaults.headers.common['Authorization'] = '';
instance.defaults.headers.post['Content-Type'] = 'application/json; charset=UTF-8';
// 自定义请求拦截器
instance.interceptors.request.use(config => {
const token = window.localStorage.getItem('token');
token && (config.headers['Authorization'] = token);
return config;
}, error => Promise.reject(error));
// 自定义响应拦截器
instance.interceptors.response.use(response => {
if (response.status === 200) {
return Promise.resolve(response.data);
}
return Promise.reject(response);
}, error => Promise.reject(error));
需要同时取消多个请求以及自动取消的应用场景,可以封装下工具方法:
export const CacheUtils = {
// 存储请求接口地址以及请求体和取消函数之间的映射关系
cache: {},
// 根据提供的键名 key 取消对应的请求,若未提供则取消全部请求
clearCache: function (key) {
if (key) {
const cancel = this.cache[key];
if (cancel && typeof cancel === 'function') {
cancel();
delete this.cache[key];
}
return;
}
Object.keys(this.cache).forEach(cacheKey => {
const cancel = this.cache[cacheKey];
cancel();
delete this.cache[cacheKey];
});
},
};
应用到请求拦截器和响应拦截器中:
import qs from 'qs';
import { CacheUtils } from './cacheUtils.js';
// 自定义请求拦截器
instance.interceptors.request.use(config => {
let cacheKey = config.url;
const token = window.localStorage.getItem('token');
token && (config.headers['Authorization'] = token);
const method = config.method.toLowerCase();
if (method === 'get' && config.params && typeof config.params === 'object') {
cacheKey += qs.stringify(config.params, { addQueryPrefix: true });
}
if (['post', 'put', 'patch'].includes(method) && config.data && typeof config.data === 'object') {
config.data = qs.stringify(config.data);
cacheKey += `_${qs.stringify(config.data, { arrayFormat: 'brackets' })}`;
}
// 每次发送请求之前将上一个未完成的相同请求进行中断
CacheUtils.cache[cacheKey] && CacheUtils.clearCache(cacheKey);
// 将当前请求所对应的取消函数存入缓存
config.cancelToken = new axios.CancelToken(function executor(c) {
CacheUtils.cache[cacheKey] = c;
});
// 临时保存 cacheKey,用于在响应拦截器中清除缓存
config.cacheKey = cacheKey;
return config;
}, error => Promise.reject(error));
// 自定义响应拦截器
instance.interceptors.response.use(response => {
// 响应接收之后清除缓存
const cacheKey = response.config.cacheKey;
delete CacheUtils.cache[cacheKey];
if (response.status === 200) {
return Promise.resolve(response.data);
}
return Promise.reject(response);
}, error => {
// 响应异常清除缓存
if (error.config) {
const cacheKey = error.config.cacheKey;
delete CacheUtils.cache[cacheKey];
}
return Promise.reject(error);
});
Fetch API:
IE
浏览器下的兼容性不容乐观,一番探索之后,发现可以通过isomorphic-fetch
或者whatwg-fetch
这两个第三方依赖来解决兼容性问题。
const url = 'http://www.some-domain.com/path/to/example';
const initData = {
method: 'POST',
body: JSON.stringify({key: value}),
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
cache: 'no-cache',
credentials: 'same-origin',
mode: 'cors',
redirect: 'follow',
referrer: 'no-referrer',
};
fetch(url, initData).then(response => response.json()).then(data => console.log(data));
// 也可以直接通过 Request 构造函数来初始化请求数据
// Request 构造函数接收两个参数
// 第一个参数表示需要获取的资源 URL 路径或者另一个嵌套的 Request 实例
// 第二个可选参数表示需要被包含到请求中的各种自定义选项
const request = new Request(url, initData);
fetch(request).then(response => response.json()).then(data => console.log(data));
在XHR
实例中可以通过abort
方法来取消请求,在Axios
中可以通过CancelToken
构造函数的参数来获得取消函数,从而通过取消函数来取消请求。但是很遗憾的是,在Fetch API
中,并没有自带的取消请求的API
供我们调用。不过令人愉悦的是,除了IE
浏览器外,其他浏览器已经为Abort API
添加了实验性支持,Abort API
允许对XHR
和fetch
这样的请求操作在未完成时进行终止。
对Abort API有兴趣的小伙伴可以自行查阅资料,自己封装下。