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 相关文章推荐
js function使用心得
May 10 Javascript
js下通过prototype扩展实现indexOf的代码
Dec 08 Javascript
中文字符串截取的js函数代码
Apr 17 Javascript
jQuery 淡出一个图像到另一个图像的实现代码
Jun 12 Javascript
深入浅析javascript中的作用域(推荐)
Jul 19 Javascript
jQuery基于函数重载实现自定义Alert函数样式的方法
Jul 27 Javascript
js学习总结_选项卡封装(实例讲解)
Jul 13 Javascript
python爬取安居客二手房网站数据(实例讲解)
Oct 19 Javascript
vue生成token保存在客户端localStorage中的方法
Oct 25 Javascript
Angular中使用better-scroll插件的方法
Mar 27 Javascript
VUE项目初建和常见问题总结
Sep 12 Javascript
JavaScript中的函数申明、函数表达式、箭头函数
Dec 06 Javascript
使用JS实现简易计算器
微信小程序实现聊天室功能
小程序实现文字循环滚动动画
React 高阶组件HOC用法归纳
Jun 13 #Javascript
React forwardRef的使用方法及注意点
原生Javascript+HTML5一步步实现拖拽排序
JS代码编译器Monaco使用方法
You might like
php实现mysql数据库备份类
2008/03/20 PHP
PHP与MySQL开发中页面乱码的产生与解决
2008/03/27 PHP
解析PHP中一些可能会被忽略的问题
2013/06/21 PHP
分享5个非常有用的Laravel Blade指令
2018/05/30 PHP
php微信公众号开发之答题连闯三关
2018/10/20 PHP
详解php伪造Referer请求反盗链资源
2019/01/24 PHP
网页的标准,IMG不支持onload标签怎么办
2006/06/29 Javascript
javascript函数中的arguments参数
2010/08/01 Javascript
javascript间隔刷新的简单实例
2013/11/14 Javascript
在WordPress中加入Google搜索功能的简单步骤讲解
2016/01/04 Javascript
js的form表单提交url传参数(包含+等特殊字符)的两种解决方法
2016/05/25 Javascript
Java  Spring 事务回滚详解
2016/10/17 Javascript
AngularJS ng-repeat指令中使用track by子语句解决重复数据遍历错误问题
2017/01/21 Javascript
微信小程序倒计时功能实现代码
2017/11/09 Javascript
基于vue中css预加载使用sass的配置方式详解
2018/03/13 Javascript
Python实现发送email的几种常用方法
2014/08/18 Python
把项目从Python2.x移植到Python3.x的经验总结
2015/04/20 Python
python实现备份目录的方法
2015/08/03 Python
Python3中的列表,元组,字典,字符串相关知识小结
2017/11/10 Python
可能是最全面的 Python 字符串拼接总结【收藏】
2018/07/09 Python
Python 3.8新特征之asyncio REPL
2019/05/28 Python
Python3之不使用第三方变量,实现交换两个变量的值
2019/06/26 Python
python双向链表原理与实现方法详解
2019/12/03 Python
基于python实现音乐播放器代码实例
2020/07/01 Python
vscode配置anaconda3的方法步骤
2020/08/08 Python
Python实现石头剪刀布游戏
2021/01/20 Python
Volcom法国官网:美国冲浪滑板品牌
2017/05/25 全球购物
意大利折扣和优惠券网站:Groupalia
2019/10/09 全球购物
仓库文员岗位职责
2014/04/06 职场文书
厨房管理计划书
2014/04/27 职场文书
建筑院校毕业生求职信
2014/06/13 职场文书
销售团队获奖感言
2014/08/14 职场文书
城管执法人员纪律作风整顿思想汇报
2014/09/13 职场文书
群众路线对照检查剖析材料
2014/10/09 职场文书
回复函范文
2015/07/14 职场文书
大学生暑期实践报告之企业经营管理
2019/08/08 职场文书