首页 > 其他分享 >记录--前端实用小技巧: 自动合并的网络请求

记录--前端实用小技巧: 自动合并的网络请求

时间:2023-08-08 18:13:29浏览次数:39  
标签:queue const 请求 -- 前端 timer 实用 params fn

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

我们经常会遇到一个场景,比如在一个列表中批量获取用户的信息。

 

如果我们一次性往后端发送几十条请求是非常愚蠢的事情。此时我们就要学会如何使用批量获取的逻辑。

但是批量获取有一个问题就是,我需要在用户列表项的上层去获取,然后再把结果分发给下层

此时的结构如下:

const List = () => {
	return itemInfoList.map((info) => <Item info={info} />)
}

这样我们就可以很方便的解决我们遇到的问题啦,因为是一个接口获取的结果嘛。

但是!这种写法不利于维护。因为 <Item /> 组件的依赖过于庞大,是一个完整的对象。对于其他组件来说很难复用,大概率这个组件就只能在一处地方用了。那么我想要复用怎么办呢?

那就是写成如下形式

const List = () => {
	return ids.map((id) => <Item id={id} />)
}

然后 <Item /> 组件内部就可以根据传入的id自行获取对应的数据,然后自我处理了。

那么不就遇到开头这个问题了嘛?如果一次渲染几十个用户组件,那么不就同时向后端发送几十个网络请求了嘛!

这时候我们需要实现一个逻辑,自动收集并合并可以被合并的网络请求。

完整代码如下:

interface QueueItem<T, R> {
  params: T;
  resolve: (r: R) => void;
  reject: (reason: unknown) => void;
}

/**
 * 创建一个自动合并请求的函数
 * 在一定窗口期内的所有请求都会被合并提交合并发送
 * @param fn 合并后的请求函数
 * @param windowMs 窗口期
 */
export function createAutoMergedRequest<T, R>(
  fn: (mergedParams: T[]) => Promise<R[]>,
  windowMs = 200
): (params: T) => Promise<R> {
  let queue: QueueItem<T, R>[] = [];
  let timer: number | null = null;

  async function submitQueue() {
    timer = null; // 清空计时器以接受后续请求
    const _queue = [...queue];
    queue = []; // 清空队列

    try {
      const list = await fn(_queue.map((q) => q.params));
      _queue.forEach((q1, i) => {
        q1.resolve(list[i]);
      });
    } catch (err) {
      _queue.forEach((q2) => {
        q2.reject(err);
      });
    }
  }

  return (params: T): Promise<R> => {
    if (!timer) {
      // 如果没有开始窗口期,则创建
      timer = window.setTimeout(() => {
        submitQueue();
      }, windowMs);
    }

    return new Promise<R>((resolve, reject) => {
      queue.push({
        params,
        resolve,
        reject,
      });
    });
  };
}

用法是:

const fetchUserInfo = createAutoMergedRequest<string, UserBaseInfo>(
  async (userIds) => {
    const { data } = await request.post('/api/user/getUserInfoList', {
      userIds,
    });

    return data;
  }
);

fetchUserInfo(1)
fetchUserInfo(2)
fetchUserInfo(3)

接下来我们来解读一下代码。

先看整体架构。 createAutoMergedRequest 函数返回了一个匿名函数,来接受参数并返回结果请求。但是需要注意的是我们定义了两个泛型 TR 。其中 createAutoMergedRequest 接受的 fn 参数的类型是 (mergedParams: T[]) => Promise<R[]> ,而返回的函数定义是 (params: T): Promise<R> 。这是因为他会自动把请求的结果拆分成独立的返回值返回到对应的调用处。

我们看返回的函数体:

if (!timer) {
    // 如果没有开始窗口期,则创建
    timer = window.setTimeout(() => {
      submitQueue();
    }, windowMs);
  }

  return new Promise<R>((resolve, reject) => {
    queue.push({
      params,
      resolve,
      reject,
    });
  });

首先判断闭包中是否存在定时器 timer, 如果没有则创建一个timer,在 windowMs 后执行 submitQueue 方法。我们把 windowMs 定义为窗口期,在这个窗口期内调用该函数的请求都会被收集起来。

然后创建返回一个promise,把参数和promise相关的下一步操作都推到 queue 中。

等到若干次调用后,定时器到时间了,唤起回调执行submitQueue 方法,我们来看看 submitQueue 的操作。

async function submitQueue() {
  timer = null; // 清空计时器以接受后续请求
  const _queue = [...queue];
  queue = []; // 清空队列
  const ret = fn(_queue.map((q) => q.params));

  try {
    const list = await fn(_queue.map((q) => q.params));
    _queue.forEach((q1, i) => {
      q1.resolve(list[i]);
    });
  } catch (err) {
    _queue.forEach((q2) => {
      q2.reject(err);
    });
  }
}

执行前我们会做一些前置工作,清理 timer, 清理 queue 并把队列里的项单独存放起来,防止影响到下一次执行。

然后我们通过 fn(_queue.map((q) => q.params)) 来把队列中的参数拿出来,传给 fn 调用。此时的 fn 就会接收到一个数组。并确保返回的结果也是一个同等大小且一一对应的数据即可。如果请求无误,我们就循环队列,把结果通过队列中记录的 resolve 把结果返回给我们之前创建的promise。

这样我们就实现了一个工具函数,我们可以在一个窗口期内收集到多个网络请求,并把他们汇聚成一个请求发送到后端。后端结果返回回来后,我们再把请求结果拆分分发给独立的调用方。

 

本文转载于:

https://juejin.cn/post/7259275893796388925

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 

标签:queue,const,请求,--,前端,timer,实用,params,fn
From: https://www.cnblogs.com/smileZAZ/p/17615060.html

相关文章

  • Docker(.Net6) 环境下使用 Haukcode.WkHtmlToPdfDotNet
     背景: 项目使用的是.Net6+Docker,需要将数据生成PDF保存到第三方文件存储服务器上。引用NuGet:Haukcode.WkHtmlToPdfDotNet 这个插件还是满好用的,支持Windows、Docker.可以直接通过Url转PDF,也可以通过Html字符,生成PDF.官方地址:https://github.com/Hak......
  • k8s 容器安全上下文
    容器安全上下文介绍kubernetes为安全运行pod及容器运行设计了安全上下文机制,该机制允许用户和管理员定义pod或容器的特权与访问控制,已配置容器与主机以及主机之上的其它容器间的隔离级别。安全上下文就是一组用来决定容器时如何创建和运行的约束条件,这些条件代表创建和运行容器时......
  • C学习2
    1、switch(整形表达式){case整形常量表达式:……;break:}2、每个case后面记得break3、default位置随便放前后都可以4、while语句里,break用于永久终止循环,continue用于终止当前循环回到判断入口5、注意清空输入缓冲区 6、几个重要的ASCII码:0(ASCII码十进制48),A(ASCII码十进制6......
  • 基于智慧路灯杆的智慧交通应用示例
    智慧路灯杆的身影已经越来越频繁出现在我们的生活之中,无论是我们开车在路上,还是行走在商业街,造型美轮美奂,功能丰富多样的智慧路灯杆,也已经成为了一道独特靓丽的街景。智慧路灯杆如何发挥其智慧功能?对我们的生活有什么提升?今天我们就结合智慧交通场景,介绍智慧路灯杆在智慧交通方面......
  • Typora+GitHub+PicGo设置
    Typora+GitHub+PicGo创建GitHub图像存储仓库新建仓库输入仓库名称image-repo选择public公开属性用户设置settings找到developersettings创建token选择classic输入名称,token过期时间,和repo,点击创建token复制保存token,后面要用PicGo设置仓库名......
  • 工业4.0 RAMI体系之运动控制规范(PLCopen SoftMotion) @Like
    工业4.0 RAMI体系之运动控制规范(PLCopenSoftMotion)@Like 目录1.运动控制1.1.自动化控制1.2.PLCopen运动控制规范1.3.PLC功能块1.3.1.功能块特性1.3.2.单轴功能块Single-Axis1.3.3.多轴功能块Multi-Axis 运动控制1.1. 自动化控制PLC、Robot、CNC机械电......
  • sql注入CTF常见考点方法总结
    SQL注入一、基本注入流程1.判断是否存在注入点(1)?id=xx不同,返回结果不同,则存在注入。(2)数字型判断:​ and1=1正常​ and1=2报错​ 则不存在注入​ 字符型判断:​ 1'and'1'='1正常​ 1'and'1'='2报错​ 则存在注入(3)判断注入点及类型:​ a'......
  • creator 3.x 2D 物理引擎 基础使用
    首先,本文基于V3.5,官方文档在这里:https://docs.cocos.com/creator/3.5/manual/zh/physics/一.明确2D物理引擎,和3D物理引擎在接口上有点区别,实际区别我也不清楚在哪里,官方文档也没说.比如:启用物理引擎PhysicsSystem2D.instance.enable=true;3D的叫......
  • Python_GUI(pySide)开发指南(@Like)
    Python_GUI(pySide)开发指南(@Like) 目录一、PythonGUI简介二、PySide6工具安装1.安装VSCode:https://code.visualstudio.com/2.安装Python:https://www.python.org/downloads/3.安装PyCharm:https://www.jetbrains.com/pycharm/4.更新pip: 命令python.exe-mpip......
  • mongodb副本集模式的单机部署+修改ip
    环境:OS:Centos7mongodb:4.4.22 1.解压[root@localhostsoft]#tar-xvfmongodb-linux-x86_64-rhel70-4.4.22.tgz[root@localhostsoft]#mvmongodb-linux-x86_64-rhel70-4.4.22/usr/local/services/mongodb 2.创建目录mkdir-p/home/middle/mongodb/data/mkdir-p/ho......