axios取消重复请求,是取消前面的请求还是后面的请求呢,成年人当然是我全都要

722

axios取消重复请求的文章很多,大部分都是用caceltoken取消前面的请求。但实际上大部分业务模式下,取消重复请求有时需要取消前面的重复请求,有时呢又应该取消后面的重复请求。举个例子,根据用户输入在onchange搜索时,就应该取消前面的请求防止前面的请求后返回,导致页面展示非预期。而当有些用户操作,比如删除新增时,忘记给按钮加loading而接口又比较慢时,导致多新增了数据,或删除时接口报错。这时更应该取消后面的重复请求。

首先我会在接口调用时传入参数,通过传入cancelType来控制取消前面还是后面的请求。

<script>
import httpRequest from '../utils/request'
export default {
  name: 'app',
  components: {
  },
  methods: {
    cancelBefore(){
      httpRequest.get('/GetPagerList',{},{cancelType: 'before'})
    },
    cancelafter(){
      httpRequest.post('/Web/yuyue',{},{cancelType: 'after'})
    } 
  }
}

取消前面的pengding请求核心是CancelToken,这个就不多说了

import axios from 'axios'
const CancelToken = axios.CancelToken

class HttpRequest {
  constructor() {
    this.baseURL = 'http://zj.jxeduyun.com/Web/'
    this.pending = {}
  }
  // 获取预设的配置
  getInsideConfig() {
    return {
      baseURL: this.baseURL,
      headers: { 'Content-Type': 'application/json;charset=utf-8' },
      timeout: 10000,
    }
  }
  // 取请求url、method、params、data等组成key,标识这次请求
  getPendingKey(config) {
    return [
      config.url,
      config.method,
      JSON.stringify(config.params),
      JSON.stringify(config.data),
    ].join('&')
  }
  // 移除pending请求,isFront 是否取消前面的请求
  removeKey(key, isFront = false) {
    if (this.pending[key] && isFront) {
      this.pending[key]('取消前面的重复请求')
    }
    delete this.pending[key]
  }

而取消后面的请求核心是直接reject

// 拦截处理
  interceptors(instance) {
    // 添加请求拦截器
    instance.interceptors.request.use(
      config => {  
        // 1、取请求标识key
        // 2、移除上一次该请求标识(如果有,则调用cancelToken取消上次请求)
        // 3、给这次请求增加cancelToken备用(如果后续有重复请求则调用)
	if(config.cancel){
          const cancelType = config.cancel
          const key = this.getPendingKey(config)
          // 取消前面的重复请求
	  if(cancelType==='before'){
            this.removeBefore(key, true)
            config.cancelToken = new CancelToken(cancel => {
              this.pending[key] = cancel
	    })
            return config
          }else if(cancelType==='after'){
	    // 取消后面的请求直接reject,要把key传下去,好在前面请求失败的时候也移除
	    if(this.pending[key]){
	      return Promise.reject({key,msg:'取消后面的请求'})
	    }else{		
	      this.pending[key]='after' //
	      return config
	    }
	  }
	}
	else{
          return config
	}
      },
      error => {
        // 对请求错误做些什么
	console.log(error)
        return Promise.reject(error)
      }
    )

    // 添加响应拦截器
    instance.interceptors.response.use(
      response => {
        // 请求返回结果,移除pending
        const key = this.getPendingKey(response.config)
        this.removeKey(key)
        if (response.status === 200) {
          return Promise.resolve(response.data)
        } else {
          return Promise.reject(response)
        }
      },
      error => {
	// 取消后面重复请求 响应错误同样要移除key
	if(error.key){
	  this.removeKey(error.key)
	} 
        // 对响应错误做些什么
        console.log(error.msg || error)
        return Promise.reject(error)
     }
   )
 } 

取消后面的重复请求时,判断pending是否有请求key,有直接reject。没有就加上,return config。特别需要注意的是取消后面的重复请求时。一定要在error时也从pending中删除。否则当接口报错时,无法继续请求。

特别注意的是,通过cancelToken取消请求,请求依然是会发送到后端的,如果后端没做处理的话,依然该干嘛干嘛。只是前端不再拿数据而已,如果要防止重复操作,用取消后面的请求更好,前面一个请求没结束的时候直接就不发起请求。 当然如果接口是严格按照rustful风格的话,只需要通过method判断,就不用传参了,get全部取消前面的重复请求,post、delete、put直接取消后面的重复请求.那将绝杀,可惜。

最后是封装的一些方法

 // 发送请求
  request(config) {
    const instance = axios.create()
    const newOptions = Object.assign(this.getInsideConfig(), config)
    this.interceptors(instance)
    return instance(newOptions)
  }

  get(url,data,config) {
    const options = Object.assign(
      {
        url,
	params: data,
        method: 'get'
      },
      config
    )
    return this.request(options)
  }
  post(url, data, config) {
    const options = Object.assign(
      {
        url,
        data,
        method: 'post'
      },
      config
    )
    return this.request(options)
  }
  // 取消全部pending请求
  cancelAll(key) {  
    for(key in this.pending){
      this.removeKey(key, true)
    }
    // 清空pengding
    this.pending = {}
  }
}
export default new HttpRequest()

cancelAll方法会取消所有pengding接口,可以在spa中路由跳转进入之前调用,减少不必要的网络开支。比如vue的beforeEach 中调用。这就是全部内容了,如果有更好的方案或想法欢迎留言讨论。