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 相关文章推荐
Extjs Ajax 乱码问题解决方案
Apr 15 Javascript
jquery图片延迟加载 前端开发技能必备系列
Jun 18 Javascript
JavaScript获取图片真实大小代码实例
Sep 24 Javascript
手机端页面rem宽度自适应脚本
May 20 Javascript
一种基于浏览器的自动小票机打印实现方案(js版)
Jul 26 Javascript
ES6中的箭头函数实例详解
Apr 06 Javascript
js学习总结之dom2级事件基础知识详解
Jul 27 Javascript
Nuxt配合Node在实际生产中的应用详解
Aug 07 Javascript
vue2.0 使用element-ui里的upload组件实现图片预览效果方法
Sep 04 Javascript
js实现导航跟随效果
Nov 17 Javascript
VueJS 取得 URL 参数值的方法
Jul 19 Javascript
11个Javascript小技巧帮你提升代码质量(小结)
Dec 28 Javascript
使用JS实现简易计算器
微信小程序实现聊天室功能
小程序实现文字循环滚动动画
React 高阶组件HOC用法归纳
Jun 13 #Javascript
React forwardRef的使用方法及注意点
原生Javascript+HTML5一步步实现拖拽排序
JS代码编译器Monaco使用方法
You might like
Look And Say 序列php实现代码
2011/05/22 PHP
php截取html字符串及自动补全html标签的方法
2015/01/15 PHP
smarty模板引擎之配置文件数据和保留数据
2015/03/30 PHP
php结合md5实现的加密解密方法
2016/01/25 PHP
PHP正则匹配反斜杠'\'和美元'$'的方法
2017/02/08 PHP
php实现通过stomp协议连接ActiveMQ操作示例
2020/02/23 PHP
jquery隔行换色效果实现方法
2015/01/15 Javascript
js实现鼠标触发图片抖动效果的方法
2015/02/27 Javascript
浅谈EasyUI中Treegrid节点的删除
2015/03/01 Javascript
javascript中Function类型详解
2015/04/28 Javascript
JavaScript forEach()遍历函数使用及介绍
2015/07/08 Javascript
jQuery+canvas实现的球体平抛及颜色动态变换效果
2016/01/28 Javascript
javascript实现瀑布流加载图片原理
2016/02/02 Javascript
JS弹出新窗口被拦截的解决方法
2016/08/09 Javascript
微信小程序 122100版本更新问题解决方案
2016/12/22 Javascript
ES6中Proxy与Reflect实现重载(overload)的方法
2017/03/30 Javascript
socket.io学习教程之基础介绍(一)
2017/04/29 Javascript
vue项目实现设置根据路由高亮对应的菜单项操作
2020/08/06 Javascript
利用node.js开发cli的完整步骤
2020/12/29 Javascript
python读取word文档的方法
2015/05/09 Python
对Python使用mfcc的两种方式详解
2019/01/09 Python
pandas把所有大于0的数设置为1的方法
2019/01/26 Python
Python依赖包整体迁移方法详解
2019/08/15 Python
python3.7通过thrift操作hbase的示例代码
2020/01/14 Python
python 爬取马蜂窝景点翻页文字评论的实现
2020/01/20 Python
Python爬虫实现模拟点击动态页面
2020/03/05 Python
python request 模块详细介绍
2020/11/10 Python
H5 canvas实现贪吃蛇小游戏
2017/07/28 HTML / CSS
英国时尚优质的女装:Hope Fashion
2018/08/14 全球购物
c++工程师面试问题
2013/08/04 面试题
给老婆大人的检讨书
2014/02/24 职场文书
党的群众路线教育实践方案
2014/05/11 职场文书
销售求职信范文
2014/05/26 职场文书
阿凡达观后感
2015/06/10 职场文书
2016年第32个教师节红领巾广播稿
2015/12/18 职场文书
2016年企业先进员工事迹材料
2016/02/25 职场文书