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

在项目开发中,为防止连续请求导致的数据混乱,本文介绍了如何在React中利用AbortController来取消和中止fetch请求。针对fetch API没有内置取消请求的特性,文章通过创建abortDispath.js文件和扩展Component来解决这个问题,确保在发起新请求前能够终止之前的请求。

场景:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值