Axios 中取消上次发起的请求

axios 中怎样取消上次发起的请求,我现在就带你研究

场景

取消上次请求的使用场景主要是频繁发起的请求导致数据错乱的问题,去抖也只能有效一点点,如果用户的网络有波动,可能会出现请求返回很慢的情况,也可能会出现第一个请求比第二个请求返回的晚的现象,导致预期数据被覆盖掉了。

解决这个问题大致有三个办法,第一个是比较粗暴的,上个请求未返回时不许发起新的请求,确实有人这么做,理由是用户的网络比较快,不会出现卡住的现象🤔;第二个办法是返回的数据中加一个数据的标识,通过判断这个标识是否为预期标识来决定是否覆盖数据,比如请求的是 type 为 2 的列表,返回的数据中也有一个 type: 2 ,这样就不会将其他 type 的数据错误覆盖了,缺点是需要有后端配合返回一个唯一请求参,不够灵活;第三种就是我们接下来要提到的通过取消上次请求的做法啦,发起新的请求时讲上次的请求取消了,便不必担心它一段时间后会自顾的返回数据对我们造成困扰啦。

解决

axios 中为我们提供了取消上次请求的钩子 CancelToken ,官方提供的例子是这样的

axios 文档描述

You can cancel a request using a cancel token.

The axios cancel token API is based on the withdrawn cancelable promises proposalopen in new window.

You can create a cancel token using the CancelToken.source factory as shown below:

const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('/user/12345', {
  cancelToken: source.token
}).catch(function (thrown) {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
    // handle error
  }
});

axios.post('/user/12345', {
  name: 'new name'
}, {
  cancelToken: source.token
})

// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');

You can also create a cancel token by passing an executor function to the CancelToken constructor:

const CancelToken = axios.CancelToken;
let cancel;

axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    // An executor function receives a cancel function as a parameter
    cancel = c;
  })
});

// cancel the request
cancel();

Note: you can cancel several requests with the same cancel token. If a cancellation token is already cancelled at the moment of starting an Axios request, then the request is cancelled immediately, without any attempts to make real request.

但是我们平时写 axios 的时候已经是封装过的函数了,这样的使用方法我们并不能直接拿来使用,在项目中的使用方式如下:

// api/list.js
import request from './request'
import axios from 'axios'

const cancelToken = axios.cancelToken

interface GetListApi {
  (data: any): any
  cancelReq?: any
}
export const getListApi: GetListApi = function (data: any) {
  return request({
    url: '/lsit',
    method: 'post',
    data,
    cancelToken: new CancelToken(function executor(c) {
      getListApi.cancelReq = c
    })
  })
}

// 拦截器配置
Service.interceptors.response.use(
  response => {
    const res: IAxiosRequest = response.data
    return CheckState(res, response.config)
  },
  error => {

    if (Axios.isCancel(error)) {
      // 本次请求已手动取消
      if (!error.message) {
        // 若没有传入取消 cancelReson,则使用默认
        error.message = 'REQUEST_MANUAL_CANCEL: This request has been manually cancelled'
      }
      // 使用 resolve 是因为此时为手动取消请求,而不是请求失败
      return Promise.resolve(error)
    }

    Message.error(error.message)
    return Promise.reject(error)
  }
)

// list.vue
import { getListApi } from '@/api/list'
async getList() {
  const cancelToken = 'xxxx_xxxx_x'
  getListApi.cancelReq && getListApi.cancelReq(cancelToken)
  const res: any = await getListApi()
  const { message = '' } = res
  if (message === cancelToken) {
    /**
     * 是手动取消的请求,
     * 如果 getListApi.cancelReq 未传参数,message 值为拦截器中定义的 
     * REQUEST_MANUAL_CANCEL: This request has been manually cancelled
     * /
  }
}

因为这样会产生一些取消的请求,在 axios 的拦截器中添加配置,标识这些请求是手动取消的,便于业务逻辑代码处判断

通过在 api 函数上新增一个方法的方式来控制取消的操作,在 api 第一次调用的时候还没有 cancelReq 这个属性,所以方法不会执行,调用执行的时候往 getListApi 上挂了 cancelReq 这个属性,下次再次调用的时候就会执行 getListApi.cancelReq() 方法了,如果上次的请求还在 pending ,那么会将上次的请求取消掉,如果上次的请求已经有结果了,那么不会有变化。

MIT Licensed | Copyright © 2017-present 进击的学霸, 自豪的使用 七牛云,已加入十年之约