资讯专栏INFORMATION COLUMN

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

3403771864 / 986人阅读

  描述如下

  我们要同时发多个相同的请求,第一个请求成功后,剩余结果都不会发出,返回结果是成果。

  假如第一个反馈失败,第二个是成功,后面就不会发出,后面都直接反馈成功。第三个才是成功的话,后面就不会在发出,后面都反馈成功。依次如此处理,直至最后一个。  

  并发: 一个接口请求还处于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));

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

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

1.png

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

2.png

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

3.png

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

4.png

  上面表示从测试结果来看是正确的,符合了并发和缓存的场景。但是为什么要缓存接口。简单来说就是,当输入关键字搜索,监听的是input事件,在你增删关键字的时候,就会出现请求参数一样的情况,因此就符合防抖+前端接口缓存的方式。遇到相同关键字直接拉之前的缓存。

  提示

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

  github-demo

  欢迎大家继续关注后续更多精彩内容。


文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/128013.html

相关文章

  • 可能是最漂亮Spring事务管理详解

    摘要:事务隔离级别定义了一个事务可能受其他并发事务影响的程度我们先来看一下并发事务带来的问题,然后再来介绍一下接口中定义了五个表示隔离级别的常量。 Java面试通关手册(Java学习指南):https://github.com/Snailclimb/Java_Guide 微信阅读地址链接:可能是最漂亮的Spring事务管理详解 事务概念回顾 什么是事务? 事务是逻辑上的一组操作,要么都执行,...

    邹立鹏 评论0 收藏0
  • 前端基本功-常见概念(一)

    摘要:前端基本功常见概念一点这里前端基本功常见概念二点这里前端基本功常见概念三点这里什么是原型链当一个引用类型继承另一个引用类型的属性和方法时候就会产生一个原型链。函数式编程是声明式而不是命令式,并且应用程序状态通过纯函数流转。 前端基本功-常见概念(一) 点这里前端基本功-常见概念(二) 点这里前端基本功-常见概念(三) 点这里 1.什么是原型链 当一个引用类型继承另一个引用类型的属性和方...

    bladefury 评论0 收藏0
  • 浏览器详解

    摘要:渲染引擎也称为呈现引擎浏览器内核,负责显示请求的内容。引擎是基于事件驱动单线程执行的,引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个线程在运行程序。 1 浏览器结构 showImg(https://segmentfault.com/img/bVk7AU); 浏览器分为以下7个部分: 用户界面 浏览器引擎:在用户界面和呈现引擎之间传送指令。 渲染引擎:也...

    Amos 评论0 收藏0

发表评论

0条评论

3403771864

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<