背景:这次使用vue3搭建的项目,依照组件化的思想进行了项目开发,后来发现一个问题,由于多个页面调用同一个接口服务,在进行路由切换之后,当前页再调用这个共用的接口服务时候,该接口服务会被重复调用,且切换多少次路由,就会重复调用多少次接口服务。通常项目中在每个请求接口的方法中添加一个请求锁,防止重复请求,但是这样比较low,代码比较冗余,于是添加一个全局的方法,就不用每次请求接口前手动编写请求锁,减少编码和工作量。

在进行服务调用中,由于前一次的请求耗时远大于后一次请求,导致页面渲染内容错误,本应该渲染最后一次的结果却被第一次请求的结果覆盖。对此,有很多解决方案:

  1. 防抖节流:可一定程度上缓解问题,但不能完全解决,请求时间过长还是会出现这个问题。
  2. 前后端配合,前台发请求时带上一个字段,后台返回结果时将该字段返回,前台判断只有最后一次请求时的数据为准,该方法能解决问题,但前后台都需要参与且代码量不小,维护成本高。
  3. 利用观察者模式,将请求顺序入栈,然后按顺序出栈。此方法可以彻底解决问题,而且也不需要后台配合,但是性能损耗大,因为需要一直监听栈的变化,而且前端维护量大,有需要的地方都需要引入。
    但是上述方案都不太适合,总要牺牲一些其他方面的效率。
    综上,目前最合适的解决方案如下:
    原理:相同请求在没有返回值之前,若再次调用则进行拦截并取消。
    在使用axios的文件中引入如下方法(这里我是将axios及限制接口重复请求方法都放在了request.js文件中):
    (1)定义移除方法
// 先定义方法,用于移除重复请求
const pending = {};
const removePending = (key, isRequest = false) => {
  if (pending[key] && isRequest) {
    pending[key]("取消重复请求!");
  }
  if (pending[key]) {
    delete pending[key];
  }
}

(2)请求-响应拦截

import axios, { CancelToken } from 'axios';  // 引入axios的第三方插件、取消重复token

 // 1. 创建axios实例
const service = axios.create({
  ...
})
 
// 2. 使用创建的axios实例生成请求拦截器
// 我这里key为 url + & + method, 可以根据项目需求自定义key键
// 每一个请求都为其创建一个key和CancelToken的实例
service.interceptors.request.use(
  config => {
    const key = config.url + '&' + config.method;
    removePending(key, true);
    config.cancelToken = new CancelToken((cancel) => {
      pending[key] = cancel;
    })
    return config;
  }
)

// 3. 使用创建的axios实例生成响应拦截器
// 若在pending中存在,则会直接cancel请求
service.interceptors.response.use(
  response => {
    const key = response.config.url + '&' + response.config.method;
    removePending(key);
    return response;
  },
  error => {
    console.log('err' + error) // for debug
  }
)

(3)完整代码

import axios, { CancelToken } from "axios" // 引入axios的第三方插件
import qs from 'qs'

import configapi from '../../public/config'

// 先定义方法,用于移除重复请求
const pending = {};
const removePending = (key, isRequest = false) => {
  if (pending[key] && isRequest) {
    pending[key]("取消重复请求!")
  }
  if (pending[key]) {
    delete pending[key]
  }
}

// create an axios instance
// 1. 创建axios实例
const service = axios.create({
  // 公共接口绑定
  baseURL: configapi.backstageIp,
  // baseURL: '/api',
  // 设置接口请求超市时间
  timeout: 135000,
  // retry: 1, // 请求次数
  // retryDelay: 1000 // 请求间隙
})

// request interceptor
// 2. 使用创建的axios实例生成请求拦截器
// 我这里key为 url + & + method, 可以根据项目需求自定义key键
// 每一个请求都为其创建一个key和CancelToken的实例
service.interceptors.request.use(
  config => {
    // // 打开加载窗口
		// Loading.open()

    // 在发送请求之前带上一些东西,config是请求的配置对象,如果直接返回就等于什么都不带
    // let token = window.localStorage.token
    // if (token) {
    //   config.headers.Authorization = 'Bearer' + token //如果token 存在,就带上token

    // } else {
    //   config.headers['token'] = '' //return
    // }

    const key = config.url + '&' + config.method;
    removePending(key, true);
    config.cancelToken = new CancelToken((cancel) => {
      pending[key] = cancel;
    })

    if (config.method === 'post') {
			config.paramsSerializer = function(params) {
				return qs.stringify(params, { arrayFormat: 'repeat' })
			}
			// config.params = qs.stringify(config.params, { indices: false });
		}

    return config
  },
	error => {
		// // 关闭加载窗口
		// Loading.close()
		// do something with request error
		// console.log(error) // for debug
		return Promise.reject(error) // 请求错误处理
	}
)

// response interceptor
// 3. 使用创建的axios实例生成响应拦截器
// 若在pending中存在,则会直接cancel请求
service.interceptors.response.use(
  /**
   * If you want to get http information such as headers or status
   * Please return  response => response
  */

	/**
   * Determine the request status by custom code
   * Here is just an example
   * You can also judge the status by HTTP Status Code
   */
  response => {
    // // 0.7 秒后关闭加载窗口
		// setTimeout(() => {
		// 	Loading.close()
		// }, 700)
    
    const key = response.config.url + '&' + response.config.method;
    removePending(key)

		return response.data
  },
	error => {
		// // 关闭加载窗口
		// Loading.close()
		// console.log('err' + error)
		// Message({
		//   message: error.message,
		//   type: 'error',
		//   duration: 5 * 1000
		// })
		return Promise.reject(error)
	}
)

export default service
Logo

前往低代码交流专区

更多推荐