vue 配置axios拦截重复的请求

描述: 拦截重复请求就是防止一时间发起多个一样的请求造成系统卡顿, 比如网速较慢时用户频繁点击发起请求, 这边我也看了很多博客学习如何配置比较好,然后看了又 不太明白 axios是如何取消之前的请求的, 原理是怎么样?? 很多博客都没说,只是贴了代码, 官网也有,但是还是只是明白了大概, 所以我这边还是自己总结一下:

可以先去看看官网描述:
官网描述

大致原理

(后面会贴源码分析,当然,那是大佬分析的,不是我这个菜鸡):

其实就是 我们 通过官网提供的 CancelToken.source 工厂方法创建 cancel token 或者 通过传递一个 executor 函数到 CancelToken 的构造函数来创建 cancel token, 然后我们请求带上这个cancleToken, 然后在需要取消的请求后面手动调用 cancel方法进行取消, 可能这样说不是很清楚吧,对于上面两种情况下面在详细说一下:

1. CancelToken.source 工厂方法创建 cancel token

const CancelToken = axios.CancelToken;
const source = CancelToken.source();
// 返回一个包含token和cancel方法的对象

这样会返回一个token和cancel方法的对象,我们就在请求的方法添加上cancleToken, 比如页面你有个请求,这边拿官网的来演示

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

// 你调用了一个get方法
axios.get('/user/12345', {
  cancelToken: source.token // 带上cancelToken
}).catch(function(thrown) {
// 取消后进入这里
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
     // 处理错误
  }
});

// 或者你是调用post
axios.post('/user/12345', {
  name: 'new name'
}, {
  cancelToken: source.token // 带上
})

// 取消请求(message 参数是可选的)
// 这里调用了取消请求,会把上面的请求取消掉, 当然这里只是演示用,实际饿哦们不是在这调用
source.cancel('Operation canceled by the user.');

2. 通过传递一个 executor 函数到 CancelToken 的构造函数
这个是通过构造函数生成cancleToken和把cancle取消函数传给我们自己定义的变量

const CancelToken = axios.CancelToken; // 构造函数
let cancel;

axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    // executor 函数接收一个 cancel 函数作为参数
    cancel = c; // 赋给我们定义的cancle,注意这个 c 其实是个函数
  })
});

// 在你需要取消的地方调用取消函数
cancel(); // 调用取消函数

大致就这样啦, 那么项目怎么全局配置,而不是这样一个请求设置一次??
这边先贴我的代码哈, 大部分逻辑也是参考网上的

说明: 我这里是通过一个数组保存请求, 然后每次在拦截里面判断是否是同一个请求, 如果是则取消上一个请求, 那么判断依据我这里是 判断了 请求方法 method, 请求地址 url和请求参数(params或者data) 要全部 一样 才算同一个请求, 不然有些请求地址一样只是参数不一样.

setting1.js

import axios from "axios";
axios.defaults.baseURL = "/api";

let pending = []; //声明一个数组用于存储每个ajax请求的取消函数和ajax标识
let cancelToken = axios.CancelToken;
let removeRepeatUrl = ever => {
  for (let p in pending) {
    // 判断是否存在重复请求
    if (
      pending[p].config &&
      pending[p].config.url === ever.url &&
      pending[p].config.method === ever.method
    ) {
      if ((isObjectValueEqual(pending[p].config), ever))
        //当当前请求在数组中存在时执行函数体
        pending[p].cancle(); //执行取消操作
      pending.splice(p, 1); //把这条记录从数组中移除
    }
  }
};

// 请求拦截器
axios.interceptors.request.use(
  config => {
    console.log("config", config);
    //在一个ajax发送前执行一下取消操作
    removeRepeatUrl({
      method: config.method,
      url: config.url,
      params: config.params,
      data: config.data
    });
    // 创建cancleToken和cancle取消请求方法, 每个请求都不一样的哦
    config.cancelToken = new cancelToken(c => {
      // 自定义唯一标识
      pending.push({
        config: {
          method: config.method,
          url: config.url,
          params: config.params,
          data: config.data
        },
        cancle: c // 
      });
    });
    return config;
  },
  err => {
    return Promise.reject(err);
  }
);

// 响应拦截器
axios.interceptors.response.use(
  res => {
    console.log("响应", res);

    removeRepeatUrl({
      method: res.config.method,
      url: res.config.url,
      params: res.config.params,
      data: res.config.data
    }); //在一个ajax响应后再执行一下取消操作,把已经完成的请求从pending中移除
    const data = res.data;
    return data;
  },
  err => {
    return Promise.reject(err);
  }
);

export const request = config => {
  return axios({
    ...config
  });
};

/**
 *比较两个对象是否相等
 * @method isObjectValueEqual
 * @param {Object} a 对象a
 * @param {Object} b 对象b
 * @return {Boolean}
 */
function isObjectValueEqual(a, b) {
  console.log(a, b);
  // 判断两个对象是否指向同一内存,指向同一内存返回true,同时比较null和undefined情况
  if (a == b) return true;
  if (a == null || a == undefined || b == null || b == undefined) {
    return false;
  }
  // 获取两个对象键值数组
  let aProps = Object.getOwnPropertyNames(a);
  let bProps = Object.getOwnPropertyNames(b);
  // 判断两个对象键值数组长度是否一致,不一致返回false
  if (aProps.length !== bProps.length) return false;
  // 遍历对象的键值
  for (let prop in a) {
    // 判断a的键值,在b中是否存在,不存在,返回false
    if (b.hasOwnProperty(prop)) {
      // 判断a的键值是否为对象,是则递归,不是对象直接判断键值是否相等,不相等返回false
      if (typeof a[prop] === "object") {
        if (!isObjectValueEqual(a[prop], b[prop])) return false;
      } else if (a[prop] !== b[prop]) {
        return false;
      }
    } else {
      return false;
    }
  }
  return true;
}

api方法

import { request } from "./setting1";
export function getList(params) {
  return request({
    url: "/list",
    method: "get",
    params: params
  });
}
export function getList1(params) {
  return request({
    url: "/list1",
    params: params,
    method: "get"
  });
}

vue文件调用:

<template>
  <div>
    <el-button type="primary"
               @click="handleGO">请求1</el-button>
    <el-button type="primary"
               @click="handleGet">请求2</el-button>
  </div>
</template>

<script>
import { getList, getList1 } from "@/api/index";
export default {
  name: "WorkspaceJsonIndex",

  data() {
    return {};
  },

  mounted() {},

  methods: {
    handleGO() {
      getList({
        requestName: "11",
      }).then((res) => {
        console.log("请求成功", res);
      });
    },
    handleGet() {
      getList1().then((res) => {
        console.log("请求成功11", res);
      });
    },
  },
};
</script>

<style lang="scss" scoped>
</style>

实测:
可以看到我开启 slow 3G将网速变慢, 然后连续请求就会取消掉之前的啦
在这里插入图片描述

详细原理:

原理这里来源: Axios用cancelToken取消未完成的异步请求
这边方便查看,转载过来了,详细请跳转链接查看哈

整体思路是 axios请求在xhrAdapter里新建了一个XHR连接,然后判断是否有cancleToken,如果有则通过config.cancelToken.promise.then的处理,来达到终止XHR连接(abort)的目的。

作为一个Promise,要触发then,这个Promise必须resolve,而这个resolve,则应该是可以由用户触发的,才能达到目的。而用户触发就是通过调用cancle函数触发。

由于每个axios.get/post都会生成一个对应的XHR连接,所以,每个连接都需要由source()工厂函数,生成新的token和对应的cancel,否则,使用相同的token,调用cancel时,会把几个连接一起停掉,这一点需要注意。

对于浏览器(区别于node),Axios创建的异步连接,本质上是新建了一个XMLHttpRequest,然后包装Promise。

// Axios > lib > adapters > xhr.js
module.exports = function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    var requestData = config.data;
    // ...
    var request = new XMLHttpRequest();
    // ...
    if (config.cancelToken) { // 如果有cancletoken
      // Handle cancellation
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
          return;
        }
 
        request.abort();// 取消请求
        reject(cancel);
        // Clean up request
        request = null;
      });
    }
    // ...
    // Send the request
    request.send(requestData);
  }
}

cancelToken.source是个工厂函数,通过它,返回token实例和一个cancel处理函数。

// Axios > lib > cancel > cancelToken.js
CancelToken.source = function source() {
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  return {
    token: token,
    cancel: cancel
  };
};

可以看到,整体思路是在xhrAdapter里新建了一个XHR连接,然后通过config.cancelToken.promise.then的处理,来达到终止XHR连接(abort)的目的。

作为一个Promise,要触发then,这个Promise必须resolve,而这个resolve,则应该是可以由用户触发的,才能达到目的。

CancelToken这个构造函数,返回的实例,包含一个promise。

// Axios > lib > cancel > CancelToken.js
function CancelToken(executor) {
  // ...
  var resolvePromise;
  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });
 
  var token = this;
  executor(function cancel(message) {
    if (token.reason) {
      // Cancellation has already been requested
      return;
    }
 
    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}

source()函数里,new CancelToken的时候,executor函数只有一个操作:cancel = c。

这里executor(function cancel(...) {...})),实际把整个函数赋值过去,即cancel = function cancel(...) {...})。然后把这个cancel函数,连同CancelToken实例,一起暴露给用户。即,必须在CancelToken实例上执行对应的cancel函数,才会起效。

总结

所以梳理一下执行流程就是:
|cancel -> resolvePromise -> then -> request.abort()
在这里插入图片描述
由于每个axios.get/post都会生成一个对应的XHR连接,所以,每个连接都需要由source()工厂函数,生成新的token和对应的cancel,否则,使用相同的token,调用cancel时,会把几个连接一起停掉,这一点需要注意。

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值