前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Web技术】2042- 前端实现并发控制网络请求

【Web技术】2042- 前端实现并发控制网络请求

作者头像
pingan8787
发布2024-05-17 13:54:38
1150
发布2024-05-17 13:54:38
举报
文章被收录于专栏:前端自习课前端自习课

有时候会遇到需要同时请求多个接口的场景,比如:获取学生列表,然后需要根据每个学生的id获取学生参加的社团的数量(意思是这么个意思,就是要依赖一个数组的每一项进行另一个请求获取其他数据)(听起来应该是后端将每个课程的人数一块返回了才正常),但是确实是有不正常的情景,即返回的学生信息不包含学生参与的社团数量,需要额外调用接口。假设有三四十个学生,那就意味着需要循环调用三四十个获取参与的社团数量的接口,显然对服务器造成的压力是很大的,显然不可以。此处就采用请求池的方法

使用Promise.all的方法:

Promise.all方法似乎是很容易想到的,将所有的Promise对象(即每个请求)都放入Promise.all中处理,等Promise.all处理结束后再往下执行

代码语言:javascript
复制
async const () => {
  const list = [xxx] // list 是课程数组
  const promiseList = list.map((item)=>{
    return axios.get(`https://xxx?id=${item.id}`)
  })

  await Promise.all(promiseList)
  // 执行后续操作
}

  // 这里请求多个接口我用下列代码来模拟:
  const fn = () => {
    const arr = []
    for(let i = 1;i < 100;i++){
      arr.push(axios.get('/test/api' + i))
    }
    await Promise.all(arr)
    // 执行后续操作
  }

接着看一下每个接口所需的时间(由于接口是乱写的,所以报错很正常,主要是看一下请求时间):

如果是使用请求池,花费的时间如下:

可以看到请求所花费的时间减少了很多

请求池:

为什么是请求池?和Promise.all有什么区别?

Promise.all是等多个请求都响应后才能触发后续操作,请求池是上一个请求响应后就可以往队列继续添加而不需要等待其他请求

参考文章[1] 最近学校参加的一个项目(小程序),就遇到了一次性发送几十个接口的情况,大大影响了性能。还在头痛怎么优化来着,今天恰巧刷到了这位老哥的文章,有了启发

请求池的主体思想是利用队列进行模拟(先进先出,即先进先发送请求),当然,不一定非得使用队列这一数据结构,这里就利用数组来模拟

代码语言:javascript
复制
import axios from 'axios'

export const handQueue = (
  list // 请求总数
) => {
  list = list || 0

  const requestQueue = (maxNum) => {
    maxNum = maxNum || 6 // 最大并发数
    const queue = [] // 请求池
    let current = 0 // 当前请求了多少条

    const dequeue = () => {
      while (current < maxNum && queue.length) {
        current++
        const currentPromise = queue.shift() // 出列
        currentPromise()
          .then(() => {
            // 成功的请求逻辑
            console.log('执行成功')
          })
          .catch((error) => {
            // 失败
            console.log(error)
          })
          .finally(() => {
            current--
            dequeue()
          })
      }
    }

    return (currentPromise) => {
      queue.push(currentPromise) // 入队
      dequeue()
    }
  }

  const enqueue = requestQueue(6)
  // console.log('enqueue:', enqueue)

  for (let i = 0; i < list; i++) {
    enqueue(() => axios.get('/api/test' + i))
  }
}
// 使用时在页面调用handQueue即可

关于请求池花费的时间可以上移回去看两张对比图

整合到项目中:

在请求池中,在拿出来队头那一个Promise对象之后,在then的回调函数中,肯定不是只局限与打印出接口成功返回的信息,往往需要结合实际进行其他操作。这里就讲一讲笔者踩的坑以及对应的解决方法: 约定:项目需求是在一个已经给定的数组中,遍历每一项,根据每一项的id调用接口获取其他信息

问题一:接口调用顺序不正常

前面说了,请求池是上一个请求响应后就执行下一个,前一个响应了并不能保证前一个接口调用就完成了(细细品一下这一句话)。每个接口的请求时间肯定是多多少少有区别的,所以并不能保证接口成功返回的顺序还是调用时的顺序。这一点也是可以得到验证的,如下:

可以发现接口返回的顺序并不是按正常的下标顺序执行的

问题二:并非任何时候都能拿到请求结果

任何时候一定能在请求池中拿到请求的结果吗? 答案是否定的 以笔者开发遇到的场景来说,我是在onLoad生命钩子中执行请求池函数的,并打印了执行结果:

代码语言:javascript
复制
const { res } = handQueue(list)
console.log(res)

由于异步是非阻塞的(在请求池中执行的就是多个网络请求),在执行打印的时候可能请求池中的请求都没来得及执行完,所以此时打印会出现拿不到值的情况 笔者的解决方法比较暴力,直接加一个setTimeout,让其500ms之后再打印,惊奇的发现500ms完全够这么多个请求执行完毕,也就兴高采烈的完成任务啦 当然,这种方案不够优雅,毕竟今天请求池里执行四五十个接口500ms够用,万一明天请求的接口变成了一百多个500ms不够用咋办,总不能将计时器的倒计时时间设置大一点吧。不仅抽象,且影响用户体验 大佬们如果有其他方案,欢迎交流

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-05-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端自习课 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 使用Promise.all的方法:
  • 请求池:
  • 整合到项目中:
    • 问题一:接口调用顺序不正常
      • 问题二:并非任何时候都能拿到请求结果
      相关产品与服务
      云开发 CloudBase
      云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档