代码片段之js限流调度器

1,811 阅读1分钟

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

前言

上一篇写了个vue2 源码解析之 nextTick反响不好, 所以写了一半的第二篇vue源码解析改成了这篇, 希望能有多一点的互动.🤕

以下保证都是我自己手敲出来的, 所以保证了原创性, 保证不了正确性.

概念

限流调度器, 本意是指对 js 的任务进行限流(同时不要做多个), 在我理解 js 中对任务限流其实主要就是对jshttp请求进行限流, 因为其他任务需要限流的场景我还没有遇到过(这种场景是肯定有的, 有遇到的欢迎和我互动啊~~). 为什么需要对http请求进行限流呢? 因为各种环境对http请求都有限制

  1. 网络速度有限制(网络没有那么快)
  2. 服务器响应的速度有限制(服务器没有那么快)
  3. 浏览器对http请求有限制
    1. 同时请求数量有限制
    2. 等待队列的请求数量有限制

下面写了个小例子, 验证第三点

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script>
      for (let i = 0; i < 2000; i++) {
        fetch(`http://127.0.0.1:3000/get?index=${i}`)
      }
    </script>
  </body>
</html>
const express = require('express')
const app = express()
const sleep = async (time) => {
  return new Promise((res) => {
    setTimeout(res, time)
  })
}
app.get('/get', async (req, res) => {
  await sleep(10000)
  res.end('')
})
app.listen(3000, () => {
  console.log('启动了')
})

image.png image.png

第二种情况(同时发大量请求, 一千多个)几乎不会出现, 所以针对第一种情况(浏览器同时只发送 6 个请求)需要对一些不重要的请求(比如预加载一百张图片)进行限流, 防止阻塞重要的请求(比如用户签到).

实现

思路

  1. 肯定得有一个缓存器, 把那些还没有发送的请求存起来
  2. 肯定有个上限, 这个上限肯定比 6 小, 当正在请求数大于这个数时, 不再请求

代码

这里没有处理边界情况, 也没有处理报错, 只是简单的代码实现

class LimitRequest {
  // 限制同时请求的数量
  limitCount = 0
  // 缓存还没有发送的请求
  cacheRequest = []
  // 当前正在请求的数量
  currentRequest = 0
  constructor(limitCount) {
    this.limitCount = limitCount
  }
  /**
   * 新增请求
   * @param {*} fn
   */
  addRequest(fn) {
    // 把这个请求缓存起来
    this.cacheRequest.push(fn)
    // 发送请求
    // 这个发送请求可以不写, 由用户自己手动去调用
    this.request()
  }
  /**
   * 从缓存中拿出一条请求, 发送
   */
  async request() {
    if (this.currentRequest >= this.limitCount) {
      // 如果当前请求数量超出了限制, 不再发送新的请求
      return
    }
    // 当前正在请求的数量加一
    this.currentRequest++
    // 拿出缓存的请求
    const fn = this.cacheRequest.shift()
    // 发送请求
    await fn()
    // 请求结束, 当前正在发送的请求减一
    this.currentRequest--
    // 空出一个名额, 可以再次请求
    this.request()
  }
}

完整代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script>
      class LimitRequest {
        // 限制同时请求的数量
        limitCount = 0
        // 缓存还没有发送的请求
        cacheRequest = []
        // 当前正在请求的数量
        currentRequest = 0
        constructor(limitCount) {
          this.limitCount = +limitCount || 3
        }
        /**
         * 新增请求
         * @param {*} fn
         */
        addRequest(fn) {
          if (typeof fn !== 'function') {
            console.log('addRequest添加请求需要传入函数')
            return
          }
          // 把这个请求缓存起来
          this.cacheRequest.push(fn)
          // 修改, 这里不去调用了, 留着用户自己在合适的时机调用
        }
        /**
         * 从缓存中拿出一条请求, 发送
         */
        async request() {
          if (this.currentRequest >= this.limitCount) {
            // 如果当前请求数量超出了限制, 不再发送新的请求
            return
          }
          // 当前正在请求的数量加一
          this.currentRequest++
          // 这里是用户主动调的, 所以一次要请求到上限
          this.request()
          try {
            // 拿出缓存的请求
            const fn = this.cacheRequest.shift()
            // 发送请求
            await fn()
          } catch (err) {
            console.log('执行fn报错', err)
          } finally {
            // 请求结束, 当前正在发送的请求减一
            this.currentRequest--
            if (this.cacheRequest.length) {
              // 空出一个名额, 并且还有请求, 可以再次请求
              this.request()
            }
          }
        }
      }
      const limitRequest = new LimitRequest(3)
      for (let i = 0; i < 2000; i++) {
        limitRequest.addRequest(async () => {
          await fetch(`http://127.0.0.1:3000/get?index=${i}`)
        })
      }
      setTimeout(() => {
        // 在3秒之后, 浏览器空闲了, 发送请求
        limitRequest.request()
      }, 3000)
    </script>
  </body>
</html>

最后

欢迎互动 欢迎加我微信呀~~~~