首页 > 其他分享 >前端如何控制并发请求

前端如何控制并发请求

时间:2024-07-11 09:30:59浏览次数:19  
标签:task const 请求 前端 并发 startIndex allResults Promise

前端如何控制并发请求

什么情况需要前端控制并发请求,在需要多次才能请求完所需数据的时候。比如接口一次返回,数据很多,让浏览器渲染卡顿甚至崩溃,这时候我们可以分批同时发出6个请求,这样就可以避免卡顿或者崩溃。

那么前端如何控制并发请求呢?

前端控制并发请求的关键思路

比如有 20 个请求,需要按照 3 个一组,第一组请求完毕后再请求第二组,以此类推。

关键思路,把请求方法和请求参数使用一个数组存起来,然后每次请求3个,请求完毕后再请求下一个3个。每组请求返回后,把结果保存起来,等所有请求都返回后,再把所有结果返回。

api 设计

pControl : 并发请求控制器, 传递最大并发数;
add : 添加请求和参数;
start : 开始请求,返回promise, 请求完毕后通过 .then 获取所有结果;

代码实现

function pControl(limit) {
  const taskQueue = [] // {task: Function, params: any[]}[]
  return {
    add,
    start
  }

  function add(task, params) {
    taskQueue.push({
      task,
      params
    })
  }

  function start() {
    return runAllTasks()
  }

  function runAllTasks() {
    const allResults = []
    return new Promise((resolve) => {

      runTask()

      function runTask() {
        if (taskQueue.length === 0) {
          // 递归结束
          return resolve(allResults)
        }
        const needRunSize = Math.min(taskQueue.length, limit)
        const tasks = taskQueue.splice(0, needRunSize)
        const promises = tasks.map(({
          task,
          params
        }) => task(params))
        Promise.all(promises).then((resList) => {
          allResults.push(...resList)
          // NOTE 递归调用的位置很关键
          runTask()
        })
      }
    })
  }
}

关键代码解读

  • pControl: 这个函数返回一个对象,包含 addstart 两个方法,add 用来添加任务和参数,start 用来开始请求,是一个闭包。

  • runAllTasks: 返回一个promise,然后在new Promise内部递归地执行runTask, runTask 通过Promise.all 执行并发请求,在Promise.all().then() 再次调用runTask,实现一组请求返回,再执行第二组请求。

实现分组等待的关键是在 .then 中递归调用。

思考: runAllTasks 可以使用循环实现吗?

能,需要使用 async 和 for 循环 + await

async function runAllTasks2() {
  const allResults = []
  const groupArr = []
  let startIndex = 0
  // 划分分组
  while (startIndex < taskQueue.length) {
    const arr = taskQueue.slice(startIndex, startIndex + limit)
    groupArr.push(arr)
    startIndex += limit
  }
  for (let index = 0; index < groupArr.length; index++) {
    const pList = groupArr[index].map(({
      task,
      params
    }) => task(params))
    const res = await Promise.all(pList)
    allResults.push(...res)
  }
  return allResults
}

在 for 中循环中不能使用 .then ,否则下一次循环不会等待上一次循环。

使用 for of 迭代实现:

async function runAllTasks2() {
  const allResults = []
  const groupArr = []
  let startIndex = 0
  // 划分分组
  while (startIndex < taskQueue.length) {
    const arr = taskQueue.slice(startIndex, startIndex + limit)
    groupArr.push(arr)
    startIndex += limit
  }
  // 迭代分组
  const it = groupArr.entries()
  for (const [key, value] of it) {
    const pList = value.map(({
      task,
      params
    }) => task(params))
    const res = await Promise.all(pList)
    allResults.push(...res)
  }
  return allResults
}

循环和 Promise 结合是怎样使用的呢?

forwhilefor...of 等命令式循环结构,想要在循环中实现等待效果,必须使用 async 函数包裹循环中的 await ,不能使用 .then

forEachmapfilter 等函数式循环结构,不支持等待效果,因为这些函数式循环结构是同步的,不支持等待。

async循环 + await 结合,实现循环之间等待效果。

promise.then递归 结合,实现循环之间等待效果。

完善 api,让其更加易用

  1. 设置默认参数:给pControl设置一个合适的默认值,设置为6,因为同一个域名在,浏览器的并发请求是 6 个。
  2. start给回调:通过回调能拿到每个分组的请求结果和知道当前完成的请求数量。

这两个改进很简单。先看用法:

const asyncTaskControl = pControl() // 默认 6 
asyncTaskControl.add(task, params1)
asyncTaskControl.add(task, params2)
// ...
asyncTaskControl.add(task, params10)

asyncTaskControl.start((res, doneSize) => {
  // 获取每组请求的结果 和当前完成了多少请求
  console.log(res) // [{index:number,result:data}] 
  console.log(doneSize)
}).then(allResults => {
  // 所有请求结果
  console.log(allResults)
})

start 回调有什么作用呢?

方便使用者拿当前并发请求的结果,方便计算完成进度。

把上述功能封装成 p-control npm 包发布

npm: p-control

可通过 npm i p-control 下载使用。

小结

  • .then 和递归结合,实现异步任务之间等待;
  • forwhile等循环和async + await结合使用,实现异步任务之间等待;
  • 使用Promise.all实现多个异步任务并发执行。

标签:task,const,请求,前端,并发,startIndex,allResults,Promise
From: https://blog.csdn.net/JackZhouMine/article/details/140258195

相关文章

  • 【uni-app+Vue3】 API请求封装:让接口调用更便捷
    前言:uni-app是一款基于Vue.js框架的跨平台开发工具,可以将代码编译成H5、小程序、App等不同平台的应用。在进行uni-app开发时,网络请求是必不可少的环节。为了方便开发,我们可以封装一些网络请求方法,以便在多个页面中复用,并且可以统一处理错误信息等问题,提高开发效率和代码质量。......
  • HTTP与HTTPS详解及其请求与响应报文的查看方法
    HTTP与HTTPS详解及其请求与响应报文的查看方法HTTP与HTTPS概述**HTTP(HyperTextTransferProtocol)**是一种用于传输超文本数据(例如HTML)的应用层协议,是Web中最基础的通信协议。它通过客户端-服务器模型工作,客户端(例如浏览器)向服务器发送请求,服务器返回相应的资源。**HTTP......
  • 前端如何取消接口调用
    ......
  • JS | fetch发送post请求
    在使用fetchAPI发送POST请求时,通常需要指定请求的方法为"POST",并且通过body参数传递要发送的数据。示例代码下面是一个简单的示例,演示如何使用fetchAPI发送POST请求:consturl='/api/endpoint';//替换为你的后端接口URLconstrequestData={mmsi:2098......
  • 常用的前端框架有哪些?
    常用的前端框架有Bootstrap框架、React框架、Vue框架、Angular框架、Foundation框架等等现在越来越多的前端框架开始出现,这为我们的项目需求带来了极大的方便。本文将为大家详细介绍几种前端框架,有一定的参考作用,希望对大家有所帮助。常用框架介绍:Bootstrap框架Bootstrap是......
  • 【libevent】bufferevent的并发访问问题
    一、问题在使用libevent实现websocket服务器时,发生了并发访问的问题。服务器程序功能主要包括实时响应Websocket客户端的控制请求,同时发送温度到客户端。现象:不加上温度发送功能时,程序正常运行加上温度发送功能后,就会出现段错误,而且检查后发现bufferevent并不为空二、原因......
  • go并发模式 o-channel
    packagemainimport("fmt""time")funcmain(){varorfunc(channels...<-chaninterface{})<-chaninterface{}or=func(channels...<-chaninterface{})<-chaninterface{}{switchlen(channels)......
  • go并发模式 or-do-channel + bridge
    packagemainimport("context""fmt")//orDonefuncorDone(ctxcontext.Context,value<-chanint)<-chanint{ordoneStream:=make(chanint)gofunc(){deferclose(ordoneStream)for{......
  • go并发模式 tee-channel
    packagemainimport("context""fmt""time")functeeChannel(ctxcontext.Context,value<-chanint)(<-chanint,<-chanint){ch1:=make(chanint)ch2:=make(chanint)gofunc(){......
  • go并发模式 pipeline
    packagemainimport("fmt""math/rand")funcmain(){pFn:=func(done<-chaninterface{},fnfunc()int)<-chanint{valueStream:=make(chanint)gofunc(){deferclose(valueStream)......