本文是深入浅出 ahooks 源码系列文章的第四篇,该系列已整理成文档-地址。觉得还不错,给个 star 支持一下哈,Thanks。

本文来探索一下 ahooks 的 useLockFn。并由此讨论一个很常见的场景,取消重复请求。

场景

试想一下,有这么一个场景,有一个表单,你可能多次提交,就很可能导致结果不正确。

解决这类问题的方法有很多,比如添加 loading,在第一次点击之后就无法再次点击。另外一种方法就是给请求异步函数添加上一个静态锁,防止并发产生。这就是 ahooks 的 useLockFn 做的事情。

useLockFn

useLockFn 用于给一个异步函数增加竞态锁,防止并发执行。

它的源码比较简单,如下所示:

  1. import { useRef, useCallback } from 'react';
  2. // 用于给一个异步函数增加竞态锁,防止并发执行。
  3. function useLockFn<P extends any[] = any[], V extends any = any>(fn: (...args: P) => Promise<V>) {
  4. // 是否现在处于一个锁中
  5. const lockRef = useRef(false);
  6. // 返回的是增加了竞态锁的函数
  7. return useCallback(
  8. async (...args: P) => {
  9. // 判断请求是否正在进行
  10. if (lockRef.current) return;
  11. // 请求中
  12. lockRef.current = true;
  13. try {
  14. // 执行原有请求
  15. const ret = await fn(...args);
  16. // 请求完成,状态锁设置为 false
  17. lockRef.current = false;
  18. return ret;
  19. } catch (e) {
  20. // 请求失败,状态锁设置为 false
  21. lockRef.current = false;
  22. throw e;
  23. }
  24. },
  25. [fn],
  26. );
  27. }
  28. export default useLockFn;

可以看到,它的入参是异步函数,返回的是一个增加了竞态锁的函数。通过 lockRef 做一个标识位,初始化的时候它的值为 false。当正在请求,则设置为 true,从而下次再调用这个函数的时候,就直接 return,不执行原函数,从而达到加锁的目的。

缺点

虽然实用,但缺点很明显,我需要给每一个需要添加竞态锁的请求异步函数都手动加一遍。那有没有比较通用和方便的方法呢?

答案是可以通过 axios 自动取消重复请求。

axios 自动取消重复请求

axios 取消请求

对于原生的 XMLHttpRequest 对象发起的 HTTP 请求,可以调用 XMLHttpRequest 对象的 abort 方法。

那么我们项目中常用的 axios 呢?它其实底层也是用的 XMLHttpRequest 对象,它对外暴露取消请求的 API 是 CancelToken。可以使用如下:

  1. const CancelToken = axios.CancelToken;
  2. const source = CancelToken.source();
  3. axios.post('/user/12345', {
  4. name: 'gopal'
  5. }, {
  6. cancelToken: source.token
  7. })
  8. source.cancel('Operation canceled by the user.'); // 取消请求,参数是可选的

另外一种使用的方法是调用 CancelToken 的构造函数来创建 CancelToken,具体使用如下:

  1. const CancelToken = axios.CancelToken;
  2. let cancel;
  3. axios.get('/user/12345', {
  4. cancelToken: new CancelToken(function executor(c) {
  5. cancel = c;
  6. })
  7. });
  8. cancel(); // 取消请求

如何自动取消重复的请求

知道了如何取消请求,那怎么做到自动取消呢?答案是通过 axios 的拦截器。

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

具体的做法如下:

第一步,定义几个重要的辅助函数。

  • generateReqKey:用于根据当前请求的信息,生成请求 Key。只有 key 相同才会判定为是重复请求。这一点很重要,而且可能跟具体的业务场景有关,比如有一种请求,输入框模糊搜索,用户高频输入关键字,一次性发出多个请求,可能先发出的请求,最后才响应,导致实际搜索结果与预期不符。这种其实就只需要根据 URL 和请求方法判定其为重复请求,然后取消之前的请求就可以了。

这里我认为,如果有需要的话,可以暴露一个 API 给开发者进行自定义重复的规则。这里我们先根据请求方法、url、以及参数生成唯一的 key 去做。

  1. function generateReqKey(config) {
  2. const { method, url, params, data } = config;
  3. return [method, url, Qs.stringify(params), Qs.stringify(data)].join("&");
  4. }
  • addPendingRequest。用于把当前请求信息添加到 pendingRequest 对象中。
  1. const pendingRequest = new Map();
  2. function addPendingRequest(config) {
  3. const requestKey = generateReqKey(config);
  4. config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {
  5. if (!pendingRequest.has(requestKey)) {
  6. pendingRequest.set(requestKey, cancel);
  7. }
  8. });
  9. }
  • removePendingRequest。检查是否存在重复请求,若存在则取消已发的请求
  1. function removePendingRequest(config) {
  2. const requestKey = generateReqKey(config);
  3. if (pendingRequest.has(requestKey)) {
  4. const cancelToken = pendingRequest.get(requestKey);
  5. cancelToken(requestKey);
  6. pendingRequest.delete(requestKey);
  7. }
  8. }

第二步,添加请求拦截器。

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

第二步,添加响应拦截器。

  1. axios.interceptors.response.use(
  2. (response) => {
  3. removePendingRequest(response.config); // 从pendingRequest对象中移除请求
  4. return response;
  5. },
  6. (error) => {
  7. removePendingRequest(error.config || {}); // 从pendingRequest对象中移除请求
  8. if (axios.isCancel(error)) {
  9. console.log("已取消的重复请求:" + error.message);
  10. } else {
  11. // 添加异常处理
  12. }
  13. return Promise.reject(error);
  14. }
  15. );

到这一步,我们就通过 axios 完成了自动取消重复请求的功能。

思考与总结

虽然可以通过类似 useLockFn 这样的 hook或方法给请求函数添加竞态锁的方式解决重复请求的问题。但这种还是需要依赖于开发者的习惯,如果没有一些规则的约束,很难避免问题。

通过 axios 拦截器以及其 CancelToken 功能,我们能够在拦截器中自动将已发的请求取消,当然假如有一些接口就是需要重复发送请求,可以考虑加一下白名单功能,让请求不进行取消。

参考

ahooks 是怎么解决用户多次提交问题?的更多相关文章

  1. java web解决表单重复提交问题

    我们大家再进行web开发的时候,必不可少会遇见表单重复提交问题.今天就来给总结如何解决表单提交问题,欢迎大家交流指正. 首先我们在讨论如何解决表单重复提交问题之前先来解决三个问题:1.什么叫表单重复提 ...

  2. 使用js控制表单重复提交(1加锁,2事件方式,3 EasyUI中解决表单重复提交)

    方法一. var flag = true; $(function() { $("#interested").click(function() { beInterested(); } ...

  3. 正确优雅地解决用户退出——JSP及Struts解决方案

    摘要       在一个有密码保护的Web应用中,正确处理用户退出过程并不仅仅只需调用HttpSession的invalidate()方法.现在大部分浏览器上都有后退和前进按钮,允许用户后退或前进到一 ...

  4. java web解决表单重复提交

    首先我们在讨论如何解决表单重复提交问题之前先来解决三个问题:1.什么叫表单重复提交?2.什么情况下会出现表单重复提交?3.什么情况需要避免表单重复提交? 什么叫表单提交问题,说白了,就是同一份信息,重 ...

  5. wex5 实战 用户点评与提交设计技巧

    最近遇到很多同学做毕业设计,其中有一项是用户点评与提交.功能并不复杂,同学们又不会,做为一个完整的功能,如果用wex5来设计开发,事半功倍.今天就以景区实战来向大家展示wex5的高效与强大.半天可以设 ...

  6. Senparc.Weixin.MP SDK 微信公众平台开发教程(七):解决用户上下文(Session)问题

    从这篇文章中我们已经了解了微信公众平台消息传递的方式,这种方式有一个先天的缺陷:不同用户的请求都来自同一个微信服务器,这使得常规的Session无法使用(始终面对同一个请求对象,况且还有对方服务器Co ...

  7. 解决用户 'IIS APPPOOL\Classic .NET AppPool' 登录失败

    解决用户 'IIS APPPOOL\Classic .NET AppPool' 登录失败 windows 7进入iis管理器 本地应用程序池 选中classic. net appPool 选择右侧的 ...

  8. php解决表单重复提交

    php解决表单重复提交时间:2015-2-28 | 评论:1条评论 | 被查看了 189 次 | 标签:php, W3cui重复提交是我们开发中会常碰到的一个问题,除了我们使用js来防止表单的重复提交 ...

  9. linux上搭建ftp、vsftp, 解决访问ftp超时连接, 解决用户指定访问其根目录,解决ftp主动连接、被动连接的问题

    linux上搭建ftp 重要 解决如何搭建ftp         解决用户指定访问其根目录         解决访问ftp超时连接         解决ftp主动连接.被动连接的问题 1.安装ftp ...

随机推荐

  1. JAVA - 如果hashMap的key是一个自定义的类,怎么办?

    JAVA - 如果hashMap的key是一个自定义的类,怎么办? 使用HashMap,如果key是自定义的类,就必须重写hashcode()和equals().

  2. 解决Windows微软账户无法登录

    更新记录 2022年4月16日:本文迁移自Panda666原博客,原发布时间:2021年8月25日. 将DNS的服务器地址修改为4.2.2.1或者4.2.2.2就可以解决无法登录microsoft账户 ...

  3. 全球共有多少MySQL实例在运行?这里有一份数据

    摘要 Shadowserver Foundation在5月31日发布了一份全网的MySQL扫描报告,共发现了暴露在公网的360万个MySQL实例.因为这份报告基数够大,而且信息也非常完整,从数据库专业 ...

  4. BUUCTF-佛系少年

    佛系少年 这题我感觉超扯,不知道当时环境是不是断网的,断网咋解密的出来.. 下载后有个压缩包,带加密的,首先16进制看看是否是真加密 这里可以看到,压缩包数据区这里都是未加密的方式 但是到了压缩包目录 ...

  5. SAP Table control

    REPORT RSDEMO02. * demostrates the use of table controls * unsolved problems: attributes left_col an ...

  6. UiPath邮件自动化

    在UiPath中下载Outlook电子邮件附件Outlook电子邮件自动化教程UiPathRPAhttps://www.bilibili.com/video/BV1oK411L72T 在UiPath中 ...

  7. 分享|智慧环保-生态文明信息化解决方案(附PDF)

    内容摘要: 生态文明建设被提到前所未有的战略高度,我们既要绿水青山,也要金山银山.宁要绿水青山,不要金山银山,而且绿水青山就是金山银山.要正确处理好经济发展同生态环境保护的关系,牢固树立保护生态环境就 ...

  8. MySQL数据检索时,sql查询的结果如何加上序号

    1.sql语法 @i:类型java定义的变量 @i:=0:这里类似给i初始化值为0 @i:=@i+1 :每次从0开始递增+1 SELECT (@i:=@i+1) as id,TDLINE FROM Y ...

  9. 自建批量更改标准BO数据程序

    by zyi

  10. 通过Go语言创建CA与签发证书

    本篇文章中,将描述如何使用go创建CA,并使用CA签署证书.在使用openssl创建证书时,遵循的步骤是 创建秘钥 > 创建CA > 生成要颁发证书的秘钥 > 使用CA签发证书.这种 ...