【cancelToken取消重复请求】
需求背景:
在购物车页面当用不停递进添加或者连续修改时容易触发两次请求,此时第一次请求的结果到来会立刻覆盖用户的第二次输入,因为此时第二次输入的接口数据还没有访问成功,此时数字会先跳变为第一次的结果,然后等第二次请求结束才会再跳变回来,这是不允许的!
虽然通过防抖可以解决左右加减按钮的显示跳变问题,但是如果用户通过输入框在第一次请求已经产生后但是还没有返回数据时立马进行第二次输入时,还是会有跳变问题。
于是就有了cancelToken这篇文章,取消相同的上一次请求达到解决这个需求的目的。
代码:
主要是先——创建一个Map用于存储每个请求的标识和取消函数const pendingMap = new Map();
然后通过——generateKey生成一个唯一的key;
最后——在请求拦截调用addPending,在响应拦截调用removePending实现。
import axios from "axios";
import Vue from 'vue'
import { Message } from "view-design";
import store from '@/store/index';
import router from '@/router/index';
import { getBrowserFingerprint } from '@/common/browser-fingerprint';
import { mdByLogin } from '@/common/common';
let config = {baseURL: process.env.VUE_APP_API,timeout: 60 * 1000, // Timeoutheaders: {'Content-Type': 'application/json'}
};
// console.log("打印Api", process.env.VUE_APP_API);// 创建一个Map用于存储每个请求的标识和取消函数
const pendingMap = new Map()// 生成请求的key
const generateKey = (config) => {const { method, url, params, data } = config// 如果是GET请求,参数在params中;如果是POST请求,参数在data中return [method, url,method === 'get' ? JSON.stringify(params) : JSON.stringify(data)].join('&')
}// 添加请求到Map
const addPending = (config) => {const key = generateKey(config)// 如果已存在相同请求,则取消之前的请求if (pendingMap.has(key)) {const cancelToken = pendingMap.get(key)cancelToken('取消重复请求') // 取消请求pendingMap.delete(key) // 从Map中删除}// 为当前请求创建取消令牌config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {// 将取消函数存入MappendingMap.set(key, cancel)})
}// 从Map中移除请求
const removePending = (config) => {const key = generateKey(config)if (pendingMap.has(key)) {pendingMap.delete(key)}
}const _axios = axios.create(config);_axios.interceptors.request.use(function (config) {// 在发送请求前,检查并取消重复请求,并将当前请求添加到MapaddPending(config)config.headers['Hipobuy-Client-Type'] = 'WEB';let userTokenInfo = localStorage.getItem('USER_TOKEN');if (userTokenInfo) {config.headers['X-Access-Token'] = userTokenInfo;}let localUserConfig = localStorage.getItem("user_language");if (localUserConfig) {localUserConfig = JSON.parse(localUserConfig);config.headers['lang'] = localUserConfig.code ? localUserConfig.code : process.env.VUE_APP_I18N_LOCALE;} else {config.headers['lang'] = process.env.VUE_APP_I18N_LOCALE;}let userCurrencyConfig = localStorage.getItem("user_currency");if (userCurrencyConfig) {userCurrencyConfig = JSON.parse(userCurrencyConfig);config.headers['currency'] = userCurrencyConfig.code ? userCurrencyConfig.code : process.env.VUE_APP_CURRENCY;} else {config.headers['currency'] = process.env.VUE_APP_CURRENCY;}var client_id = Vue.$cookies.get('_ga') || "";if (client_id.length > 6 && client_id.substring(0, 6) == 'GA1.1.') {config.headers['x-client-id'] = client_id.substring(6);}// 本地是否存在,存在就用,不存在重新获取const browserLocal = localStorage.getItem('BROWSER_RESULT');if (browserLocal) {config.headers['browser_device_id'] = browserLocal;} else {getBrowserFingerprint().then((browserResult) => {if (browserResult && browserResult.visitorId) {localStorage.setItem('BROWSER_RESULT', browserResult.visitorId);config.headers['browser_device_id'] = browserResult.visitorId;}}).catch((error) => {console.log('获取浏览器指纹信息失败:', error);});}if (config.url == '/user/register') {let asb = mdByLogin(`${config.data.email}${config.data.password}`);config.headers['signture'] = asb.signture;config.headers['timestamp'] = asb.timestamp;}return config;},function (error) {// Do something with request errorreturn Promise.reject(error);}
);// Add a response interceptor
_axios.interceptors.response.use(function (response) {// 请求完成,移除Map中的记录removePending(response.config)// Do something with response dataif (response.data.code == 200 || response.data.code == 201 || response.data.code == 0) {return response.data;} else if (response.data.code == 401) {store.commit('resetUserInfo');router.push({path: '/login'});} else if (response.data.code == 1002) {return response.data;} else if (response.data.code == 1010) {// 禁搜状态return response.data;} else {// 当messageState 状态是true的时候显示弹窗,处理多个请求,弹窗出多个错误信息if (store.state.app.messageState) {store.commit('setMessageState');Message.error({content: response.data.message,background: true,duration: 8,closable: true,onClose: () => {store.commit('setMessageState');}})}return Promise.reject(response.data);}return Promise.reject(response.data);
},function (error) {console.log(error.response);// Do something with response errorif (error.response.status == 401) {store.commit('resetUserInfo');let filterRouter = ['/', '/login', '/register', '/forgot', '/loginToken', '/goods', '/product', '/estimation', '/shopWindow', '/notice', '/help', '/issueView', '/beginner-guide'];let currentPath = window.location.pathname;let isValueIncluded = filterRouter.some(route => {if (route === '/') {// 首页只匹配完全等于 '/' 的路径return currentPath === '/';} else {// 其他路径使用 startsWith 匹配return currentPath.startsWith(route);}});// 调试信息// console.log('当前路径:', currentPath);// console.log('是否在白名单:', isValueIncluded);// console.log('白名单列表:', filterRouter);// 特殊处理:如果是产品页面且是用户主动操作(如加入购物车、购买),则跳转登录if (currentPath.startsWith('/product') && error.config && error.config.url &&(error.config.url.includes('/addV2') || error.config.url.includes('/buy') || error.config.url.includes('/cart'))) {router.push({path: '/login',query: { ...router.currentRoute.query, redirection: router.currentRoute.fullPath }});} else if (!isValueIncluded) {// 其他页面不在白名单中,跳转登录router.push({path: '/login',query: { ...router.currentRoute.query, redirection: router.currentRoute.fullPath }});}// 在白名单中的页面(包括产品页面非用户操作),不跳转登录}if (error.response.data.code == 5000) {// 风控弹框return Promise.reject(error.response.data);} else if (error.response.data.code == 8000) {// paypal支付弹框return Promise.reject(error.response.data);} else if (error.response.data.code == 8001) {// klarna支付异常弹框return Promise.reject(error.response.data);}return Promise.reject(error);}
);export default _axios;