终止请求
需求
在项目开发过程中,会有取消已发送请求的要求。
比如
- 进入 A 页面接口响应缓慢,切换B页面时,需要终止 A 页面的接口,防止请求超时等问题
- 时序控制问题
- 页面中请求同一接口三次,分别是 A B C
- 响应顺序是 A C B ,B 把 C 的结果给覆盖了
- 如果不时序控制,则数据会异常
XMLHttpRequest
XMLHttpRequest 本身支持请求中断(abort)
const xhr = new XMLHttpRequest();
xhr.method = "GET";
xhr.url = "https://slowmo.glitch.me/5000";
xhr.open(method, url, true);
xhr.send();
// 中断请求
xhr.abort();
Fetch
中断一个 fetch 请求,主要使用 AbortController
实现
AbortController
和 AbortSignal
这些 API 是由 DOM 标准规范 提供的,而不是由语言本身提供的。
// 创建 AbortController 的实例
const controller = new AbortController();
const signal = controller.signal;
// 监听 abort 事件,在 controller.abort() 执行后执行回调打印
signal.addEventListener("abort", () => {
console.log(signal.aborted); // true
});
// 触发中断
controller.abort();
使用 AbortController 中断 fetch 请求
- 在公共文件中创建 Signal 和停止 Signal 的方法并导出
// common.js
const signalCache = {};
// 创建一个signal
export function newSignal(key) {
// 用于中断请求,避免数据加载时间过长时,同一接口先请求却后返回数据带来的数据错位问题
if ("AbortController" in window) {
signalCache[key + "Controller"] = new AbortController();
}
return signalCache[key + "Controller"];
}
// 执行abort方法,停止指定key的AbortController
export function stopSignal(key) {
if ("AbortController" in window) {
signalCache[key + "Controller"] && signalCache[key + "Controller"].abort();
delete signalCache[key + "Controller"];
}
}
- 向 fetch option 中添加 signal 并捕获 AbortError 的异常
...
export default function request(url, options) {
...
let newOptions = { ...options };
// 配置signal
if(newOptions.body && newOptions.body.signal) {
const signal = newOptions.body.signal;
delete newOptions.body.signal;
newOptions = { ...newOptions, signal };
}
return fetch(url, newOptions)
.then((res) => {})
// 如果请求完成了,调用abort()不会发生错误
// 如果请求没有完成,那么Fetch就会抛出一个DOMException异常,异常的name属性值为"AbortError",因此要用catch捕获异常
.catch((err) => {
if (err.name === 'AbortError') {
console.log('Fetch aborted');
} else {
console.log('Another error');
}
}
- 创建一个需要中断的接口,并为该接口指定一个唯一 key
import { newSignal } from './common.js';
dispatch({
type: 'exampleModel/fetchMyList',
payload: {
...params,
signal: newSignal('myList').signal,
},
});
...
- 发起中断请求
import { stopSignal } from "./common.js";
stopSignal("myList");
参见
Axios
- 项目示例
// request.js
axios.post("/list", obj, {
onUploadProgress: (progress) => {
//处理进度
},
cancelToken: new axios.CancelToken(function excutor(c) {
//把每一个请求cancel函数都集中起来,调用cancel函数可中断请求
dispatch({
type: "app/setCancelToken",
payload: c,
});
}),
}).catch(err => {
if (err && err.message === '终止请求') return;
};
// app.js
...
namespace: "app",
state: {
cancelTokenList: [],//请求取消队列
},
reducers: {
// 设置取消
setCancelToken(state, payload) {
let cancelTokenList = state.cancelTokenList;
cancelTokenList.push(payload);
return { ...state, cancelTokenList };
},
// 清空
clearCencelToken(state) {
return { ...state, cancelTokenList: [] };
}
},
...
- 取消调用
// index.js
componentWillUnmount() {
const { app: { cancelTokenList } ,dispatch} = this.props;
cancelTokenList.map((d,i)=>{
d('终止请求');
console.log('终止请求',new Date().getTime());
});
dispatch({ type: 'app/clearCencelToken'});
}