接口太慢,前端怎么优雅地实现一个“请求队列”?

AI 概述
一个即插即用的请求队列如何使用?发生了什么?控制台输出类似这样:它是如何工作的? 前端开发经常遇到这样一个场景: 页面一加载,需要同时发 10 个请求,结果页面卡住,服务器也快崩了。 用户可以批量操作,一次点击触发了几十个上传文件的请求,浏览器直接转圈圈。 当后端处理不过来时,前端一...
目录
文章目录隐藏
  1. 一个即插即用的请求队列
  2. 如何使用?
  3. 它是如何工作的?

接口太慢,前端怎么优雅地实现一个“请求队列”?

前端开发经常遇到这样一个场景:

  • 页面一加载,需要同时发 10 个请求,结果页面卡住,服务器也快崩了。
  • 用户可以批量操作,一次点击触发了几十个上传文件的请求,浏览器直接转圈圈。

当后端处理不过来时,前端一股脑地把请求全发过去,只会让情况更糟。

核心思想就一句话:

不要一次性把所有请求都发出去,让它们排队,一个一个来,或者一小批一小批来。

这就好比超市结账,只有一个收银台,却来了 100 个顾客。最好的办法就是让他们排队,而不是一拥而上。我们的“请求队列”就是这个“排队管理员”。

一个即插即用的请求队列

不用复杂的分析,直接复制下面的 RequestPool 类到我们的项目里。它非常小巧,只有不到 40 行代码。

/**
 * 一个简单的请求池/请求队列,用于控制并发
 * @example
 * const pool = new RequestPool(3); // 限制并发数为 3
 * pool.add(() => myFetch('/api/1'));
 * pool.add(() => myFetch('/api/2'));
 */
class RequestPool {
 /**
   * @param {number} limit - 并发限制数
   */
 constructor(limit = 3) {
    this.limit = limit; // 并发限制数
    this.queue = [];    // 等待的请求队列
    this.running = 0;   // 当前正在运行的请求数
  }

 /**
   * 添加一个请求到池中
   * @param {Function} requestFn - 一个返回 Promise 的函数
   * @returns {Promise}
   */
 add(requestFn) {
    return new Promise((resolve, reject) => {
      this.queue.push({ requestFn, resolve, reject });
      this._run(); // 每次添加后,都尝试运行
    });
  }

 _run() {
    // 只有当 正在运行的请求数 < 限制数 且 队列中有等待的请求时,才执行
    while (this.running < this.limit && this.queue.length > 0) {
      const { requestFn, resolve, reject } = this.queue.shift(); // 取出队首的任务
      this.running++;

      requestFn()
        .then(resolve)
        .catch(reject)
        .finally(() => {
          this.running--; // 请求完成,空出一个位置
          this._run();   // 尝试运行下一个
        });
    }
  }
}

如何使用?

假设你有一个请求函数 mockApi,它会模拟一个比较慢的接口。

// 1.模拟一个慢接口
function mockApi(id) {
    const delay = Math.random()*1000+500;// 随机延迟 0.5 ~ 1.5 秒
    console.log(`[${id}] 请求开始...`);
    return new Promise(resolve => {
        setTimeout(() => {
            console.log(`[${id}] 请求完成!`);
            resolve(`任务 ${id} 的结果`);
        }, delay);
   });
}
// 2。创建一个请求池,限制并发为 2
const pool = new RequestPool(2);

// 3.把你的请求扔进去
for(let i = 1; i <= 6; i++) {
    pool.add(() => mockApi(i))
      .then(result => {
          console.log(`[${i}]收到结果: ${result}`);
    });
}

发生了什么?

当你运行上面的代码,你会看到:

  1. [1] 和 [2] 的请求几乎同时开始。
  2. [3][4][5][6] 在乖乖排队。
  3. 当 [1] 或 [2] 中任意一个完成后,队列中的 [3] 马上就会开始。
  4. 整个过程,同时运行的请求数永远不会超过 2 个

控制台输出类似这样:

[1] 请求开始...
[2] 请求开始...
// (此时 3, 4, 5, 6 在排队)

[1] 请求完成!
[1] 收到结果: 任务 1 的结果
[3] 请求开始...  // 1 号完成,3 号立刻补上

[2] 请求完成!
[2] 收到结果: 任务 2 的结果
[4] 请求开始...  // 2 号完成,4 号立刻补上

...

它是如何工作的?

  1. add(requestFn): 你扔给它的不是一个已经开始的请求,而是一个“启动器”函数 () => mockApi(i)。它把这个“启动器”放进 queue 数组里排队。
  2. _run(): 这是管理员。它会检查:
    • 现在有空位吗?(running < limit
    • 有人在排队吗?(queue.length > 0
    • 如果两个条件都满足,就从队首叫一个号(queue.shift()),让它开始工作(执行 requestFn()),并且把正在工作的计数 running 加一。
  3. .finally(): 这是最关键的一步。每个请求不管是成功还是失败,最后都会执行 finally 里的代码。它会告诉管理员:“我完事了!”,然后把 running 减一,并再次呼叫管理员 _run() 来看看能不能让下一个人进来。

这样就形成了一个完美的自动化流程:完成一个,就自动启动下一个

以后再遇到需要批量发请求的场景,别再用 Promise.all 一股脑全发出去了。

把上面那段小小的 RequestPool 代码复制到你的项目里,用它来包裹我们的请求函数。只需要设置一个合理的并发数(比如 2 或 3),就能在不修改后端代码的情况下,大大减轻服务器的压力,让我们的应用运行得更平稳。

这是一种简单、优雅且非常有效的前端优化手段。

以上关于接口太慢,前端怎么优雅地实现一个“请求队列”?的文章就介绍到这了,更多相关内容请搜索码云笔记以前的文章或继续浏览下面的相关文章,希望大家以后多多支持码云笔记。

「点点赞赏,手留余香」

0

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 admin@mybj123.com 进行投诉反馈,一经查实,立即处理!
重要:如软件存在付费、会员、充值等,均属软件开发者或所属公司行为,与本站无关,网友需自行判断
码云笔记 » 接口太慢,前端怎么优雅地实现一个“请求队列”?

发表回复