js 并发请求处理

简介

在开发过程中,我们经常需要处理多个并发请求。并发请求可以提高系统的性能,但也需要注意控制并发数,避免过多请求导致性能问题。

两种处理方式

可以使用以下两种方式来处理并发请求:

方式一

function sendRequest(requestList, limits, callback) {
  // 参数说明
  // requestList:请求列表。
  // limits:最大并发数,默认为 3。
  // callback:请求完成后的回调函数,参数为一个包含所有请求结果的数组。

  // 1. 将请求列表浅拷贝一份,用于存储未执行的请求。
  const promises = requestList.slice()

  // 2. 计算最大并发数,取规定并发数与请求数中最小的数。
  const concurrentNum = Math.min(limits, requestList.length)

  // 3. 初始化一个变量,用于记录当前并发数。
  let concurrentCount = 0

  // 4. 启动当前能执行的任务。
  const runTaskNeeded = () => {
    let i = 0
    // 启动当前能执行的任务
    while (i < concurrentNum) {
      i++
      runTask()
    }
  }

  // 5. 取出任务并且执行任务。
  const runTask = () => {
    // 删除并返回第一个元素
    const task = promises.shift()
    // 执行任务
    task && runner(task)
  }

  // 6. 执行器
  // 执行任务,同时更新当前并发数
  const runner = async (task) => {
    try {
      // 并发数+1
      concurrentCount++
      // 执行任务
      await task()
    } catch (error) {
      // 错误时处理逻辑
    } finally {
      // 并发数-1
      concurrentCount--
      // 捞起下一个任务
      picker()
    }
  }

  // 7. 捞起下一个任务
  const picker = () => {
    // 任务队列里还有任务并且此时还有剩余并发数的时候 执行
    if (concurrentCount < limits && promises.length > 0) {
      // 继续执行任务
      runTask()
    // 队列为空的时候,并且请求池清空了,就可以执行最后的回调函数了
    } else if (promises.length == 0 && concurrentCount == 0) {
      // 执行结束
      callback && callback()
    }
  }

  // 8. 入口执行
  runTaskNeeded()
}

方式二

async function sendRequest(requestList, limits, callback) {
  // 参数说明
  // requestList:请求列表。
  // limits:最大并发数,默认为 3。
  // callback:请求完成后的回调函数,参数为一个包含所有请求结果的数组。

  // 1. 初始化一个 promise 队列,用于存储所有请求的 promise。
  const promises = []

  // 2. 初始化一个 Set 集合,用于存储当前正在执行的请求。
  const pool = new Set() // set也是Iterable<any>[]类型,因此可以放入到race里

  // 3. 循环执行以下步骤:
  //   从请求列表中取出一个请求。
  //   如果当前并发数未达到限制,则执行该请求。
  //   将该请求加入并发池。

  for (let request of requestList) {
    // 开始执行前,先await 判断 当前的并发任务是否超过限制
    if (pool.size >= limits) {
      // 这里因为没有try catch ,所以要捕获一下错误,不然影响下面微任务的执行
      await Promise.race(pool)
        .catch(err => err)
    }

    const promise = request() // 拿到promise

    // 删除请求结束后,从pool里面移除
    const cb = () => {
      pool.delete(promise)
    }

    // 注册下then的任务
    promise.then(cb, cb)

    pool.add(promise)
    promises.push(promise)
  }

  // 4. 等最后一个for await 结束,这里是属于最后一个 await 后面的 微任务
  // 注意这里其实是在微任务当中了,当前的promises里面是能确保所有的promise都在其中(前提是await那里命中了if)

  Promise.allSettled(promises).then(callback, callback)
}

对比

两种处理方式的共同点是:

  • 都可以限制最大并发数,避免过多请求导致性能问题。

  • 都可以按照请求列表的顺序返回请求结果。

两种处理方式的区别是:

  • 第一种处理方式使用循环来执行请求,因此当请求列表很大时,可能会导致内存占用过多。

  • 第二种处理方式使用 Set 集合来存储当前正在执行的请求,避免了内存占。

如果觉得文章还行,可以留下你的一个小小的👍。

  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值