JS前端并发多个相同的请求控制为只发一个请求方式

目录
  • 描述如下
  • 老版本cachedAsync
  • 进阶版本
  • 测试cacheAsync
    • 快速搭建一个服务器
    • 客户端
    • 提示

描述如下

  • 同时发多个相同的请求,如果第一个请求成功,那么剩余的请求都不会发出,成功的结果作为剩余请求返回
  • 如果第一个请求失败了,那么接着发编号为2的请求,如果请求成功,那么剩余的请求都不会发出,成功的结果作为剩余请求返回
  • 如果第二个请求失败了,那么接着发编号为3的请求,如果请求成功,那么剩余的请求都不会发出,成功的结果作为剩余请求返回
  • ...以此递推,直到遇到最坏的情况需要发送最后一个请求

并发: 一个接口请求还处于pending,短时间内就发送相同的请求

async function fetchData (a)  {
    const data = await fetch('//127.0.0.1:3000/test')
    const d = await data.json();
    console.log(d);
    return d;
}
fetchData(2) // 编号 1
fetchData(2) // 2
fetchData(2) // 3
fetchData(2) // 4
fetchData(2) // 4
fetchData(2) // 5
fetchData(2)
fetchData(2)

老版本cachedAsync

我之前使用过vue的缓存函数缓存成功的请求, 实现是这样的。下面的cachedAsync只会缓存成功的请求,如果失败了,直接拉起新的请求。但是如果是上面的并发场景,相同的请求因为无法命中缓存,会出现连续发送三个请求的问题,无法处理这种并发的场景。

const cachedAsync = function(fn) {
    const cache = Object.create(null);
    return async str => {
        const hit = cache[str];
        if (hit) {
            return hit;
        }
        // 只缓存成功的Promise, 失败直接重新请求
        return (cache[str] = await fn(str));
    };
};
const fetch2 = cachedAsync(fetchData)
fetch2(2);
fetch2(2);
fetch2(2);

进阶版本

首先缓存是必须的,那么我们只要处理怎么控制并发即可。可以有这么一个思路

  • 每个请求都返回一个新的Promise, Promise的exector的执行时机,通过一个队列保存。
  • 当队列长度为1的时候,执行一次请求,如果请求成功,那么遍历队列中的exector,拿到请求的结果然后resolve。
  • 如果请求失败了,那么就把这个Promise reject掉,同时出栈。然后递归调用next
  • 直到exector队列清空为止
  const cacheAsync = (promiseGenerator, symbol) => {
    const cache = new Map();
    const never = Symbol();
    return async (params) => {
      return new Promise((resolve, reject) => {
      // 可以提供键值
        symbol = symbol || params;
        let cacheCfg = cache.get(symbol);
        if (!cacheCfg) {
          cacheCfg = {
            hit: never,
            exector: [{ resolve, reject }],
          };
          cache.set(symbol, cacheCfg);
        } else {
          // 命中缓存
          if (cacheCfg.hit !== never) {
            return resolve(cacheCfg.hit)
          }
          cacheCfg.exector.push({ resolve, reject });
        }
        const { exector } = cacheCfg;
        // 处理并发,在请求还处于pending过程中就发起了相同的请求
        // 拿第一个请求
        if (exector.length === 1) {
          const next = async () => {
            try {
              if (!exector.length) return;
              const response = await promiseGenerator(params);
              // 如果成功了,那么直接resolve掉剩余同样的请求
              while (exector.length) { // 清空
                exector.shift().resolve(response);
              }
              // 缓存结果
              cacheCfg.hit = response;
            } catch (error) {
              // 如果失败了 那么这个promise的则为reject
              const { reject } = exector.shift();
              reject(error);
              next(); // 失败重试,降级为串行
            }
          };
          next();
        }
      });
    };
  };

测试cacheAsync

需要测试的场景

  • 请求接口随机出现成功或者失败
  • 成功预期结果,剩余的请求都不会发出
  • 失败重试,接着发下一个请求

快速搭建一个服务器

const koa = require("koa");
const app = new koa();
function sleep(seconds) {
 return new Promise((resolve, reject) => {
   setTimeout(resolve, seconds);
 });
}
app.use(async (ctx, next) => {
 if (ctx.url === "/test") {
   await sleep(200);
   const n = Math.random();
   // 随机挂掉接口
   if (n > 0.8) {
       ctx.body = n;
   } else {
       ctx.status = 404
       ctx.body = ''
   }
   next();
 }
});
app.listen(3000, "127.0.0.1", () =>
 console.log("listening on 127.0.0.1:3000")
);

客户端

  var fetch2 = cacheAsync(fetchData, "test2");
  async function fetchData(a) {
    const data = await fetch("//127.0.0.1:3000/test");
    const d = await data.json();
    console.log(d);
    return d;
  }
   // 并发6个相同的请求
  console.log(fetch2(2));
  console.log(fetch2(2));
  console.log(fetch2(2));
  console.log(fetch2(2));
  console.log(fetch2(2));
  console.log(fetch2(2));

看下测试结果,刷新下页面

第一次运气很好,第一次接口就请求成功,只发送了一个请求

第二次测试运气不好,最后一个请求才成功,也是最差的场景

第三次测试,请求第三次成功了

测试下缓存 在控制台主动请求fetch2,成功命中。

从测试结果来看是正确的,符合了并发和缓存的场景。有人会问为什么要缓存接口,举个场景。输入关键字搜索,监听的是input事件,在你增删关键字的时候,就会出现请求参数一样的场景,这时候就符合防抖+前端接口缓存的方式。遇到相同关键字直接拉之前的缓存。

提示

这个缓存因为是闭包的方式,因此刷新页面缓存也失效了。不过我认为这个是理应如此,因为大部分场景刷新页面,就是要重置状态,如果要持久化,还不如保存到本地存储。

github-demo

以上就是JS前端并发多个相同的请求控制为只发一个请求的详细内容,更多关于JS并发多相同请求控制为一个的资料请关注我们其它相关文章!

(0)

相关推荐

  • JavaScript多并发问题如何处理

    经常在写代码的时候碰到这样的场景:页面初始化时显示loading页,同时启动多个ajax并发请求获取数据,当每个ajax请求返回时结束loading. 举个例子,一个下订单的页面,要查询常用地址信息.商品信息.地市信息-而这些请求都是异步的,希望等到所有数据加载完成后再允许用户操作. 要实现这个场景容易碰到的一个问题就是多并发怎么控制?下面是一些解决方法和思路: 并行改为串行 如果业务逻辑本身是串行的,但是提供的请求方式又是异步的,可以考虑此方法. 但本场景显然不是这种情况,这样做大大降低了页面

  • 如何利用JS实现一个可左右拉伸的滑块效果

    目录 前言 需求 方案 源码 后言 总结 前言 上月我一朋友,让我帮他实现一个效果,话说是他公司产品觉得自家项目的制作gif功能用户体验差,看到了别人的效果,于是就"挥刀霍霍"向了我那朋友,具体我们看下效果. 别人家的效果 自家的效果 用的是 element ui的 滑块 果然还是别人家的效果,话说这个忙还是得帮,下面开始正题 需求 动手之前我们先捋一捋需求 滑块不能超出临界值(不能滑出轨道) 手动进行向左.向右.滑块整体左右滑动 鼠标对滑块左按钮.右按钮.中间按钮 分别可以向左拉伸.

  • JavaScript/TypeScript 实现并发请求控制的示例代码

    场景 假设有 10 个请求,但是最大的并发数目是 5 个,并且要求拿到请求结果,这样就是一个简单的并发请求控制 模拟 利用 setTimeout 实行简单模仿一个请求 let startTime = Date.now(); const timeout = (timeout: number, ret: number) => { return (idx?: any) => new Promise((resolve) => { setTimeout(() => { const compa

  • JavaScript使用promise处理多重复请求

    一.为什么要写这个文章? 处理重复请求的文章想必大家也看过了很多,大多数都是分为在response返回之前发现重复请求就return掉的和使用节流/防抖来间接规避用户频繁操作两种版本的.最近在使用的过程的中,发现这两个版本在某些场景下还是有些局限性. 二.问题场景 如图,我这个h5的页面,顶部和底部都要显示这个名片组件.这些名片的信息是通过一个接口来获取的,当这个组件在当前页面被初始化时,就会发生两次重复的请求. 这时会面临几个抉择: 1. 不对重复请求做任何处理. 缺点1:造成不必要的资源浪费

  • JS前端并发多个相同的请求控制为只发一个请求方式

    目录 描述如下 老版本cachedAsync 进阶版本 测试cacheAsync 快速搭建一个服务器 客户端 提示 描述如下 同时发多个相同的请求,如果第一个请求成功,那么剩余的请求都不会发出,成功的结果作为剩余请求返回 如果第一个请求失败了,那么接着发编号为2的请求,如果请求成功,那么剩余的请求都不会发出,成功的结果作为剩余请求返回 如果第二个请求失败了,那么接着发编号为3的请求,如果请求成功,那么剩余的请求都不会发出,成功的结果作为剩余请求返回 ...以此递推,直到遇到最坏的情况需要发送最后

  • JS实现合并两个数组并去除重复项只留一个的方法

    本文实例讲述了JS实现合并两个数组并去除重复项只留一个的方法.分享给大家供大家参考,具体如下: //It's merge arr1 and arr2 , delete the same element only leave one //It's only apdapter array. If object, no. //The sequence of the two array is not required. mergeArray:function (arr1, arr2){ for (var

  • 详解nginx的请求限制(连接限制和请求限制)

    一,背景 我们经常会遇到这种情况,服务器流量异常,负载过大等等.对于大流量恶意的攻击访问,会带来带宽的浪费,服务器压力,影响业务,往往考虑对同一个ip的连接数,并发数进行限制.http_limit_conn_module 模块来实现.该模块可以根据定义的键来限制每个键值的连接数,如同一个IP来源的连接数.并不是所有的连接都会被该模块计数,只有那些正在被处理的请求(这些请求的头信息已被完全读入)所在的连接才会被计数.http_limit_req_module 模块来实现,该模块可以通过定义的 键值

  • 字节跳动面试之如何用JS实现Ajax并发请求控制

    前言 讲真的,最近也很迷茫.关于技术.关于生活吧.也找了很多在大厂的朋友去聊,想需求一些后期发展的思路.这其中也聊到了面试,聊到了招聘中会给面试者出的一些题目.我正好也好久没面试了,就从中选了几道.最近也会陆续出一系列关于一些面试问题的解析. 今天这道是字节跳动的: 实现一个批量请求函数 multiRequest(urls, maxNum),要求如下: • 要求最大并发数 maxNum • 每当有一个请求返回,就留下一个空位,可以增加新的请求 • 所有请求完成后,结果按照 urls 里面的顺序依

  • JS前端接口请求参数混淆方案分享

    目录 写在前面 什么接口的参数需要做处理 参数处理 Aes加密 Rsa加密 签名验证 处理时机 后端实现 Rsa解密 Aes解密 处理中间件 路由中使用 写在前面 在一些接口请求的场景中,我们希望携带的数据不希望是以明文的方式提交的,也就是需要对参数做一些混淆或者加密处理,后端拿到数据后再进行解密,得到真实数据. 其目的是为了保护数据的安全,以及提高被识破成明文的门槛.在例如用户登录的接口请求中,如果账号和密码是以明文传输的,会容易导致一些安全性问题,同时,我们也不希望谁都可以伪造参数对接口发起

  • Node.js 与并发模型的详细介绍

    目录 进程 线程 内核态线程 用户态线程 轻量级进程(LWP) 小结 协程 I/O 模型 阻塞 I/O 非阻塞 I/O 同(异)步 I/O Node.js 的并发模型 总结 前言: Node.js 现在已成为构建高并发网络应用服务工具箱中的一员,何以 Node.js 会成为大众的宠儿?本文将从进程.线程.协程.I/O 模型这些基本概念说起,为大家全面介绍关于 Node.js 与并发模型的这些事. 进程 我们一般将某个程序正在运行的实例称之为进程,它是操作系统进行资源分配和调度的一个基本单元,一般

  • JS前端广告拦截实现原理解析

    这篇文章主要介绍了JS前端广告拦截实现原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 主流的浏览器,默认都开启了广告过滤,这对于用户(浏览者)来说,不但加快了访问网页的速度,而且也避免了勿点一些垃圾色情的东西,可以说绿色了网络环境. 第一.对于正常的广告拦截前端开发需要注意的是: 在请求图片与js文件.接口.文件内容最好不要包含ad.guanggao等关键词,可能被拦截 我们可以用一个请求来判断浏览器有没有开启广告拦截,如果我们需要插入

  • 浅析Django 接收所有文件,前端展示文件(包括视频,文件,图片)ajax请求

    如果是后台上传文件: setting配置: STATIC_URL = '/static/' STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static'), os.path.join(BASE_DIR, "media"), ] # Django用户上传的都叫media文件 MEDIA_URL = "/media/" # media配置,用户上传的文件都默认放在这个文件夹下 MEDIA_ROOT = os.path.join

  • js前端传json后台接收‘‘被转为quot的问题解决

    一.产生原因 前端传json格式数据,后台接收却发现有一堆& quot;,但是如果后台接收参数用@RequestBody注解,则不会出现这个问题,出现这一问题的原因就是后台没有按照json格式去接收参数,按照json接收参数的前提是请求头 参数Content-Type:application/json,如此一来,后台框架才知道如何去处理参数,但有时候遇到的需求无法这么写,例如发送下载请求:         json格式参数最常见的是发送ajax请求,但是ajax无法触发浏览器下载机制,故不支持下

  • js前端对于大量数据的展示方式及处理方法

    最近暂时脱离了演示项目,开始了公司内比较常见的以表单和列表为主的项目. 干一个,爱一个了.从开始的觉得自己都做了炫酷的演示项目了,这对我来说就是个小意思,慢慢也开始踩坑,有了些经验总结可谈. 现下不得不说是个数据的时代,有数据就必定有前端来展示. 杂乱的数据通过数据分析(未碰到的点,不讲请搜),提炼出业务相关的数据维度,而前端所做的就是把这些一个个数据通过不同维度(key-value)的描述来展示到页面上. 除去花哨的展示方式(图表等),展示普通的大量列表数据有两种常用方式,分页和触底加载(滚动

随机推荐