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 滑入滑出效果实现代码
Mar 27 Javascript
网站页面自动跳转实现方法PHP、JSP(下)
Aug 01 Javascript
JQuery实现用户名无刷新验证的小例子
Mar 22 Javascript
纯JavaScript实现的分页插件实例
Jul 14 Javascript
JavaScript构造函数详解
Dec 27 Javascript
移动端横屏的JS代码(beta)
May 16 Javascript
JS Array创建及concat()split()slice()的使用方法
Jun 03 Javascript
jquery 动态合并单元格的实现方法
Aug 26 Javascript
AngularJS实现的生成随机数与猜数字大小功能示例
Dec 25 Javascript
使用express搭建一个简单的查询服务器的方法
Feb 09 Javascript
Vue-cli3简单使用(图文步骤)
Apr 30 Javascript
vue如何清除浏览器历史栈
May 25 Vue.js
使用JS实现简易计算器
微信小程序实现聊天室功能
小程序实现文字循环滚动动画
React 高阶组件HOC用法归纳
Jun 13 #Javascript
React forwardRef的使用方法及注意点
原生Javascript+HTML5一步步实现拖拽排序
JS代码编译器Monaco使用方法
You might like
《神奇女侠:血脉》神力女超人大战犯罪公司
2020/04/09 欧美动漫
如何使用jQuery+PHP+MySQL来实现一个在线测试项目
2015/04/26 PHP
PHP加密解密实例分析
2015/12/25 PHP
php语言的7种基本的排序方法
2020/12/28 PHP
Zend Framework数据库操作方法实例总结
2016/12/11 PHP
PHP封装cURL工具类与应用示例
2019/07/01 PHP
PHP的cookie与session原理及用法详解
2019/09/27 PHP
javascript 通用loading动画效果实例代码
2014/01/14 Javascript
jQuery事件用法实例汇总
2014/08/29 Javascript
jQuery添加和删除指定标签的方法
2015/12/16 Javascript
javascript中加var和不加var的区别 你真的懂吗
2016/01/06 Javascript
简介AngularJS中$http服务的用法
2016/02/06 Javascript
javascript截图 jQuery插件imgAreaSelect使用详解
2016/05/04 Javascript
Node.js返回JSONP详解
2016/05/18 Javascript
Active控件问题小结(附解决办法)
2016/06/09 Javascript
原生JS实现风箱式demo,并封装了一个运动框架(实例代码)
2016/07/22 Javascript
JavaScript与Java正则表达式写法的区别介绍
2017/08/15 Javascript
利用jsonp解决js读取本地json跨域的问题
2018/12/11 Javascript
重学JS 系列:聊聊继承(推荐)
2019/04/11 Javascript
深入剖析JavaScript instanceof 运算符
2019/06/14 Javascript
原生JavaScript实现日历功能代码实例(无引用Jq)
2019/09/23 Javascript
Vue使用轮询定时发送请求代码
2020/08/10 Javascript
深入Python函数编程的一些特性
2015/04/13 Python
Python爬虫设置代理IP的方法(爬虫技巧)
2018/03/04 Python
JSON文件及Python对JSON文件的读写操作
2018/10/07 Python
python统计中文字符数量的两种方法
2019/01/31 Python
python元组的概念知识点
2019/11/19 Python
美国手工艺品市场的领导者:Annie’s
2019/04/04 全球购物
网络安全类面试题
2015/08/01 面试题
会计专业应届生求职信
2013/11/24 职场文书
学校节能减排方案
2014/06/13 职场文书
知识竞赛拉拉队口号
2014/06/16 职场文书
2014年中秋节活动总结
2014/08/29 职场文书
博士生专家推荐信
2014/09/26 职场文书
《鲁滨逊漂流记》之六读后感(4篇)
2019/09/29 职场文书
提取视频中的音频 Python只需要三行代码!
2021/05/10 Python