Axios取消重复请求的方法实例详解


Posted in Javascript onJune 15, 2021

前言

在 Web 项目开发过程中,我们经常会遇到重复请求的场景,如果系统不对重复的请求进行处理,则可能会导致系统出现各种问题。比如重复的 post 请求可能会导致服务端产生两笔记录。那么重复请求是如何产生的呢?这里我们举 2 个常见的场景:

  • 假设页面中有一个按钮,用户点击按钮后会发起一个 AJAX 请求。如果未对该按钮进行控制,当用户快速点击按钮时,则会发出重复请求。
  • 假设在考试结果查询页面中,用户可以根据 “已通过”、“未通过” 和 “全部” 3 种查询条件来查询考试结果。如果请求的响应比较慢,当用户在不同的查询条件之前快速切换时,就会产生重复请求。

既然已经知道重复请求是如何产生的,也知道了它会带来一些问题。接下来,阿宝哥将以 Axios 为例,带大家来一起解决重复请求的问题。

一、如何取消请求

Axios 是一个基于 Promise 的 HTTP 客户端,同时支持浏览器和 Node.js 环境。它是一个优秀的 HTTP 客户端,被广泛地应用在大量的 Web 项目中。对于浏览器环境来说,Axios 底层是利用 XMLHttpRequest 对象来发起 HTTP 请求。如果要取消请求的话,我们可以通过调用 XMLHttpRequest 对象上的 abort 方法来取消请求:

let xhr = new XMLHttpRequest();
xhr.open("GET", "https://developer.mozilla.org/", true);
xhr.send();
setTimeout(() => xhr.abort(), 300);

而对于 Axios 来说,我们可以通过 Axios 内部提供的 CancelToken 来取消请求:

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

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

source.cancel('Operation canceled by the user.'); // 取消请求,参数是可选的

此外,你也可以通过调用 CancelToken 的构造函数来创建 CancelToken,具体如下所示:

const CancelToken = axios.CancelToken;
let cancel;

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

cancel(); // 取消请求

现在我们已经知道在 Axios 中如何使用 CancelToken 来取消请求了,那么 CancelToken 内部是如何工作的呢?这里我们先记住这个问题,后面阿宝哥将为你们揭开 CancelToken 背后的秘密。接下来,我们来分析一下如何判断重复请求。

二、如何判断重复请求

当请求方式、请求 URL 地址和请求参数都一样时,我们就可以认为请求是一样的。因此在每次发起请求时,我们就可以根据当前请求的请求方式、请求 URL 地址和请求参数来生成一个唯一的 key,同时为每个请求创建一个专属的 CancelToken,然后把 key 和 cancel 函数以键值对的形式保存到 Map 对象中,使用 Map 的好处是可以快速的判断是否有重复的请求:

import qs from 'qs'

const pendingRequest = new Map();
// GET -> params;POST -> data
const requestKey = [method, url, qs.stringify(params), qs.stringify(data)].join('&'); 
const cancelToken = new CancelToken(function executor(cancel) {
  if(!pendingRequest.has(requestKey)){
    pendingRequest.set(requestKey, cancel);
  }
})

当出现重复请求的时候,我们就可以使用 cancel 函数来取消前面已经发出的请求,在取消请求之后,我们还需要把取消的请求从 pendingRequest 中移除。现在我们已经知道如何取消请求和如何判断重复请求,下面我们来介绍如何取消重复请求。

三、如何取消重复请求

因为我们需要对所有的请求都进行处理,所以我们可以考虑使用 Axios 的拦截器机制来实现取消重复请求的功能。Axios 为开发者提供了请求拦截器和响应拦截器,它们的作用如下:

  • 请求拦截器:该类拦截器的作用是在请求发送前统一执行某些操作,比如在请求头中添加 token 字段。
  • 响应拦截器:该类拦截器的作用是在接收到服务器响应后统一执行某些操作,比如发现响应状态码为 401 时,自动跳转到登录页。

3.1 定义辅助函数

在配置请求拦截器和响应拦截器前,阿宝哥先来定义 3 个辅助函数:

generateReqKey:用于根据当前请求的信息,生成请求 Key;
function generateReqKey(config) {
  const { method, url, params, data } = config;
  return [method, url, Qs.stringify(params), Qs.stringify(data)].join("&");
}

addPendingRequest:用于把当前请求信息添加到pendingRequest对象中;

const pendingRequest = new Map();
function addPendingRequest(config) {
  const requestKey = generateReqKey(config);
  config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {
    if (!pendingRequest.has(requestKey)) {
       pendingRequest.set(requestKey, cancel);
    }
  });
}

removePendingRequest:检查是否存在重复请求,若存在则取消已发的请求。

function removePendingRequest(config) {
  const requestKey = generateReqKey(config);
  if (pendingRequest.has(requestKey)) {
     const cancelToken = pendingRequest.get(requestKey);
     cancelToken(requestKey);
     pendingRequest.delete(requestKey);
  }
}

创建好 generateReqKey、addPendingRequest 和 removePendingRequest 函数之后,我们就可以设置请求拦截器和响应拦截器了。

3.2 设置请求拦截器

axios.interceptors.request.use(
  function (config) {
    removePendingRequest(config); // 检查是否存在重复请求,若存在则取消已发的请求
    addPendingRequest(config); // 把当前请求信息添加到pendingRequest对象中
    return config;
  },
  (error) => {
     return Promise.reject(error);
  }
);

3.3 设置响应拦截器

axios.interceptors.response.use(
  (response) => {
     removePendingRequest(response.config); // 从pendingRequest对象中移除请求
     return response;
   },
   (error) => {
      removePendingRequest(error.config || {}); // 从pendingRequest对象中移除请求
      if (axios.isCancel(error)) {
        console.log("已取消的重复请求:" + error.message);
      } else {
        // 添加异常处理
      }
      return Promise.reject(error);
   }
);

由于完整的示例代码内容比较多,阿宝哥就不放具体的代码了。感兴趣的小伙伴,可以访问以下地址浏览示例代码。

完整的示例代码:https://gist.github.com/semlinker/e426780664f0186db434882f1e27ac3a

这里我们来看一下 Axios 取消重复请求示例的运行结果:

Axios取消重复请求的方法实例详解

从上图可知,当出现重复请求时,之前已发送且未完成的请求会被取消掉。下面我们用一张流程图来总结一下取消重复请求的处理流程:

Axios取消重复请求的方法实例详解

最后,我们来回答前面留下的问题,即 CancelToken 内部是如何工作的?

四、CancelToken 的工作原理

在前面的示例中,我们是通过调用 CancelToken 构造函数来创建 CancelToken 对象:

new axios.CancelToken((cancel) => {
  if (!pendingRequest.has(requestKey)) {
    pendingRequest.set(requestKey, cancel);
  }
})

所以接下来,我们来分析 CancelToken 构造函数,该函数被定义在 lib/cancel/CancelToken.js 文件中:

// lib/cancel/CancelToken.js
function CancelToken(executor) {
  if (typeof executor !== 'function') {
    throw new TypeError('executor must be a function.');
  }

  var resolvePromise;
  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });

  var token = this;
  executor(function cancel(message) { // 设置cancel对象
    if (token.reason) {
      return; // Cancellation has already been requested
    }
    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}

由以上代码可知,cancel 对象是一个函数,当我们调用该函数后,会创建 Cancel 对象并调用 resolvePromise 方法。该方法执行后,CancelToken 对象上 promise 属性所指向的 promise 对象的状态将变为 resolved。那么这样做的目的是什么呢?这里我们从 lib/adapters/xhr.js 文件中找到了答案:

// lib/adapters/xhr.js 
if (config.cancelToken) {
  config.cancelToken.promise.then(function onCanceled(cancel) {
    if (!request) { return; }
    request.abort(); // 取消请求
    reject(cancel);
    request = null;
  });
}

看完上述的内容,可能有的小伙伴还不是很能理解 CancelToken 的工作原理,所以阿宝哥又画了一张图来帮助大家理解 CancelToken 的工作原理:

Axios取消重复请求的方法实例详解

五、总结

本文介绍了在 Axios 中如何取消重复请求及 CancelToken 的工作原理,在后续的文章中,阿宝哥将会介绍在 Axios 中如何设置数据缓存,感兴趣的小伙伴不要错过哟。如果你想了解 Axios 中 HTTP 拦截器及 HTTP 适配器的设计与实现,可以阅读77.9K 的 Axios 项目有哪些值得借鉴的地方 这篇文章。

到此这篇关于Axios取消重复请求的文章就介绍到这了,更多相关Axios取消重复请求内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

六、参考资源

Javascript 相关文章推荐
基于jQuery的Tab选项框效果代码(插件)
Mar 01 Javascript
js拖拽一些常见的思路方法整理
Mar 19 Javascript
JS获取当前日期时间并定时刷新示例
Mar 04 Javascript
使用jQuery中的wrap()函数操作HTML元素的教程
May 24 Javascript
js无法获取到html标签的属性的解决方法
Jul 26 Javascript
移动开发之自适应手机屏幕宽度
Nov 23 Javascript
JS 对java返回的json格式的数据处理方法
Dec 05 Javascript
浅谈Angular4实现热加载开发旅程
Sep 08 Javascript
vue.js打包之后可能会遇到的坑!
Jun 03 Javascript
深入浅析var,let,const的异同点
Aug 07 Javascript
使vue实现jQuery调用的两种方法
May 12 jQuery
JS原型和原型链原理与用法实例详解
Feb 05 Javascript
使用JS实现简易计算器
微信小程序实现聊天室功能
小程序实现文字循环滚动动画
React 高阶组件HOC用法归纳
Jun 13 #Javascript
React forwardRef的使用方法及注意点
原生Javascript+HTML5一步步实现拖拽排序
JS代码编译器Monaco使用方法
You might like
是否存在第一台收音机的说法
2021/03/01 无线电
php下关于Cannot use a scalar value as an array的解决办法
2010/08/08 PHP
PHP中使用curl入门教程
2015/07/02 PHP
Ecshop 后台添加新功能栏目及管理权限设置教程
2017/11/21 PHP
PHP正则之正向预查与反向预查讲解与实例
2020/04/06 PHP
Laravel实现批量更新多条数据
2020/04/06 PHP
Javascript 继承实现例子
2009/08/12 Javascript
js getBoundingClientRect() 来获取页面元素的位置
2010/11/25 Javascript
jquery制作搜狐快站页面效果示例分享
2014/02/21 Javascript
基于iframe实现类似于ajax的页面无刷新
2014/05/31 Javascript
JavaScript实现的伸展收缩型菜单代码
2015/10/14 Javascript
JavaScript 中对象的深拷贝
2016/12/04 Javascript
深入浅析Vue不同场景下组件间的数据交流
2017/08/15 Javascript
详解angular路由高亮之RouterLinkActive
2018/04/28 Javascript
[01:31]DOTA2上海特级锦标赛 SECRET战队完整宣传片
2016/03/16 DOTA
python版本五子棋的实现代码
2018/12/11 Python
python实现AES加密解密
2019/03/28 Python
python如何统计代码运行的时长
2019/07/24 Python
详解Python并发编程之创建多线程的几种方法
2019/08/23 Python
python神经网络编程实现手写数字识别
2020/05/27 Python
keras中epoch,batch,loss,val_loss用法说明
2020/07/02 Python
美国创意之家:BulbHead
2017/07/12 全球购物
在对linux系统分区进行格式化时需要对磁盘簇(或i节点密度)的大小进行选择,请说明选择的原则
2012/01/13 面试题
日语专业推荐信
2013/11/12 职场文书
2014年上半年工作自我评价
2014/01/18 职场文书
知识竞赛主持词
2014/03/26 职场文书
政府个人对照检查材料思想汇报
2014/10/08 职场文书
大足石刻导游词
2015/02/02 职场文书
神农溪导游词
2015/02/11 职场文书
留学推荐信(中英文版)
2015/03/26 职场文书
家装电话营销开场白
2015/05/29 职场文书
委托开发合同书(标准版)
2019/08/07 职场文书
分享几个JavaScript运算符的使用技巧
2021/04/24 Javascript
用Python监控你的朋友都在浏览哪些网站?
2021/05/27 Python
Win11开始菜单添加休眠选项
2022/04/19 数码科技
Tomcat执行startup.bat出现闪退的原因及解决办法
2022/04/20 Servers