React - 实现fetch取消、中止请求

    技术2022-07-11  100

    场景:

    项目开发过程中有时会遇到这种情况:两次查询请求相隔时间很短时,由于接口异步,第一次请求可能会覆盖第二次请求返回数据,所以需要在第二次请求前先将第一次请求中止,话不多说,实现如下: 关于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 }, }; })
    Processed: 0.009, SQL: 9