React - 实现fetch取消、中止请求
2020-07-01 本文已影响0人
麻辣烫不烫
场景:
项目开发过程中有时会遇到这种情况:两次查询请求相隔时间很短时,由于接口异步,第一次请求可能会覆盖第二次请求返回数据,所以需要在第二次请求前先将第一次请求中止,话不多说,实现如下:
关于axios取消请求网上有很多,可自信百度,本文主要针对于fetch请求,由于fetch并没有 "取消请求" 方法,目前通过AbortController()实现
项目:Ant.Design Pro + umijs + dva
新建一个abortDispath.js文件
export const abortDispatch = (payload, dispatch) => {
if (payload && payload.signal) {
const error = 'success! request is aborted';
if (payload.signal.aborted) {
// eslint-disable-next-line prefer-promise-reject-errors
return Promise.reject(error); // 通过reject达到中止请求目的
}
const cancellation = new Promise((_, reject) => {
payload.signal.addEventListener('abort', () => {
// eslint-disable-next-line prefer-promise-reject-errors
reject(error);
});
});
return Promise.race([cancellation, dispatch(payload)]); //Promise.race比较中止和接口请求快慢,谁快返回谁
}
return dispatch(payload);
};
新建一个扩展Component
import React from 'react';
/**
* 用于组件卸载时自动cancel所有注册的promise
*/
export default class EnhanceComponent extends React.Component {
constructor(props) {
super(props);
this.abortSignalControllers = [];
}
componentWillUnmount() {
this.abortSignalControl(); // 页面卸载后自动中止所有请求
}
/**
* 注册control
*/
bindSignalControl = () => {
let registerSignal = '';
if ('AbortController' in window) {
const controller = new AbortController();
const { signal } = controller;
registerSignal = signal;
this.abortSignalControllers.push(controller);
}
return registerSignal;
};
/**
* 取消signal对应的Promise的请求
* @param {*} signal
*/
abortSignalControl(signal) {
if (signal !== undefined) {
const idx = this._findSignalControl(signal);
if (idx !== -1) {
const control = this.abortSignalControllers[idx];
control.abort();
this.abortSignalControllers.splice(idx, 1);
}
} else {
this.abortSignalControllers.forEach(control => {
control.abort();
});
this.abortSignalControllers = [];
}
}
_findSignalControl(signal) {
const idx = this.abortSignalControllers.findIndex(controller => controller.signal === signal);
return idx;
}
}
demo.js
export default class Demo extends EnhanceComponent{
constructor(props) {
super(props);
this.state = {
signal: this.bindSignalControl(),
};
}
search = () => {
this.abortSignalControl(this.state.signal); // 请求前先中止上一次的请求
const payload = {
...其他参数
signal: this.bindSignalControl()
}
this.setState({
signal: payload.signal, // 保存最新的signal
});
abortDispatch(payload, query =>
dispatch({
type: 'xxx',
payload: {...query},
callback: (data, aborted) => {
if (!aborted) {
// !aborted避免页面卸载后或者中止请求后进行this.setState
}
}
})
}
}
request.js文件中需要将signal放在options中,例如
在errorHandler方法中对Aborted接口进行特殊处理
const errorHandler = error => {
const { response, data, name } = error || {};
const { status, url = '', statusText } = response || {};
// 如果是终止请求,默认返回成功
if (name === 'AbortError') {
return {
code: 0, // 请求成功
aborted: true, // callback页面是否中止请求成功
};
}
......
return { response, data };
}
const request = extend({
errorHandler,
prefix: '/api',
});
request.interceptors.request.use((url, options) => {
return {
url,
options: {
...options,
signal: options.signal
},
};
})