axios使用之cancelToken

304 阅读2分钟

2021031315.gif

在开发PC端项目时,经常会出现上一个请求还在pending状态,用户又进行相同点击,出现多次发送相同请求的情况。为了减少请求次数,减少服务器压力,前端工程师们做了比较多的尝试。

  • 一种方案是在操作按钮或者页面上加上loading蒙层,阻止用户点击。这种方式早期用的比较多,操作简单,不展开描述。
  • 另一种方式就是今天在此记录的,axios使用之cancelToken,使用cancelToken取消请求的场景还分为如下两种:
    • 对于上一个请求还在pending状态,相同的请求进行拦截
    • 对于输入框实时请求,直接取消在pending中的请求,重新发送最新的请求
// utils/request.ts
// 接口默认pending状态下重复的请求不会发送
// 如果需要输入实时请求的接口,可以在请求参数中配置 remote:true

import axios from 'axios';
import { Message, Loading } from 'element-ui'
import baseURL from '../config';

let loading:any
let pending:Array<any> = []
let cancelToken = axios.CancelToken
let cancel:any 

const service: any = axios.create({
  baseURL, 
  timeout: 60000, 
});
// serverless接口校验单点登录需要带如下参数
service.defaults.headers.common['x-client-ajax'] = '1';
service.defaults.withCredentials = true;
// 请求拦截器
service.interceptors.request.use(
  (config: any) => {
    let {method,url,baseURL} = config
    let id = JSON.stringify({method,url:baseURL+url}) 
    let index = pending.findIndex(p=>p.id===id)
    if(config.remote){// 如远程下拉组件可带remote,true会重新发送,旧请求会被取消
      removePending(id)
    }
    config.cancelToken = new cancelToken((c)=>{
      pending.push({ id, cancel: c })
      cancel = c
    })
    if(index>-1 && !config.remote){ // 如果不是远程请求,则会控制重复的请求不发送
      cancel()
      pending.splice(index, 1);
    }
    loading = loadingOpen(config.loading)
    return config;
  },
  (error: any) => {
    // Do something with request error
    console.log(error); // for debug
    Promise.reject(error);
  },
);

// 响应拦截器
service.interceptors.response.use((res: any) => {
  let {method,url} = res.config;
  let id = JSON.stringify({method,url}); 
  removePending(id); // 删除请求队列
  loadingClose(); // 关闭全局loading
  if (res.status === 418) return login();// 单点登录鉴权,判断status 418
  const { responseType } = res.config;
  const { resultCode,errorMsg,data } = res.data;
  if (responseType === 'blob') return res;// 如果responseType为blob(导出接口),没有返回code值
  if(resultCode === 0){ // 寻常接口判断resultCode
    return Promise.resolve(data)
  }else{
    Message.error('接口繁忙:'+(errorMsg||'查询失败'))
    return Promise.reject(errorMsg)
  }
},
(error: any) => { 
  if (error && error.response && error.response.status === 418) {
    return login();// 单点登录鉴权
  }
  if (axios.isCancel(error)){
    return new Promise(()=>{}) // cancelToken
  }
  Message.error(error.message)
  return Promise.reject(error); // 接口错误
}
);

// 登陆校验
const login = () => {
  const origin = location.origin; // 这里是你想要用户登录成功后浏览器跳转回来的页面
  location.href = `http://example.com/login?redirect=${encodeURIComponent(origin)}`;
};

let removePending = (_id:string) => {
  let index = pending.findIndex(p=>p.id===_id)
  if(index > -1){
    pending[index].cancel()
    pending.splice(index, 1); //把这条记录从数组中移除
  }      
}

let loadingOpen = (loading:boolean)=>{
  let instance:any
  if(loading){
    instance = Loading.service({
      lock:true,
      text:'loading',
      background:'transparent'
    })
  }
  return instance
}

let loadingClose = ()=>{
  loading && loading.close()
}


export default service;

  • 下面是使用案例
// api/user.ts
import request from '@/utils/request';

export default {
  // 在输入实时获取情况下remote设置为true
  userInfo: (data:any,remote:Boolean): Promise<typing.AjaxPromise> => {
    return request({
      url: '/user/userInfo',
      method: 'POST',
      data,
      remote
    })
  }
};

每天进步一点点