前端进阶:从请求竞态到并发控制(系统学习笔记)

前言

今天主要围绕前端开发中一个非常核心但又容易被忽视的问题展开:

请求竞态 & 并发控制

这类问题在实际项目(尤其是搜索、列表加载、数据请求)中非常常见,同时也是面试高频考点。

本文会从基础到进阶,系统梳理:

  • 什么是请求竞态
  • 如何解决竞态问题
  • AbortController 的底层原理
  • React 中如何优雅处理请求
  • 什么是并发请求
  • 如何实现并发控制(核心算法)

一、什么是请求竞态(Race Condition)

典型场景:搜索框

用户输入:

复制代码
a → ab → abc

前端发出请求:

复制代码
req1: a
req2: ab
req3: abc

但返回顺序可能是:

复制代码
req3 → req1 → req2

最终 UI 显示的是旧数据(比如 ab 或 a)。

本质

请求的返回顺序 ≠ 请求的发送顺序


二、解决请求竞态的三种方案

方案1:请求打标(版本控制)

思路

每个请求分配唯一 ID(或时间戳),只处理"最新请求"的结果。

示例代码
js 复制代码
let currentRequestId = 0;

function search(keyword) {
  const requestId = ++currentRequestId;

  fetch(`/api?q=${keyword}`)
    .then(res => res.json())
    .then(data => {
      if (requestId !== currentRequestId) return;
      render(data);
    });
}
优点
  • 简单稳定
  • 不依赖浏览器能力
缺点
  • 请求仍然发送(浪费资源)

方案2:取消请求(AbortController)

思路

每次新请求前取消旧请求。

js 复制代码
let controller;

function search(keyword) {
  if (controller) controller.abort();

  controller = new AbortController();

  fetch(`/api?q=${keyword}`, {
    signal: controller.signal
  });
}

注意需要过滤取消错误:

js 复制代码
.catch(err => {
  if (err.name === 'AbortError') return;
});
优点
  • 真正取消请求
  • 更节省资源

方案3:防抖(Debounce)

思路

用户停止输入一段时间后才发请求,从源头减少请求数量。


三、AbortController 底层原理

核心机制

  1. fetch 监听 signal
  2. abort() 触发事件
  3. 浏览器网络层中断请求

本质

JS 发出信号 → 浏览器执行中断

不同阶段的行为

阶段 行为
请求未发送 直接取消
请求中 中断连接
响应中 停止读取数据

四、React 中的正确写法

错误示例

jsx 复制代码
useEffect(() => {
  fetch(`/api?q=${keyword}`)
    .then(res => res.json())
    .then(data => setData(data));
}, [keyword]);

问题:

  • 请求竞态
  • 组件卸载后仍 setState(内存泄漏)

正确写法

jsx 复制代码
useEffect(() => {
  const controller = new AbortController();

  fetch(`/api?q=${keyword}`, {
    signal: controller.signal
  })
    .then(res => res.json())
    .then(data => setData(data))
    .catch(err => {
      if (err.name === 'AbortError') return;
      console.error(err);
    });

  return () => {
    controller.abort();
  };
}, [keyword]);

核心点

  • 每次 effect 执行 → 创建 controller
  • cleanup 函数 → 取消上一次请求

五、什么是并发请求

定义

多个请求同时需要执行,而不是互相替代。

示例

js 复制代码
fetch('/user');
fetch('/posts');
fetch('/notifications');

和竞态的区别

类型 特点 是否需要取消
竞态 新请求替代旧请求 是
并发 多请求同时需要 否

六、并发问题的核心

并发场景要解决的不是"取消",而是:

  1. 结果管理
  2. 错误隔离
  3. 并发数量控制

七、实现并发控制(重点)

目标

限制同时最多 N 个请求。

核心思想

任务池 + 补位机制

完整实现(推荐掌握)

js 复制代码
function limitRequests(tasks, limit) {
  return new Promise((resolve) => {
    let i = 0;
    let finished = 0;
    const results = [];

    function run() {
      if (i >= tasks.length) return;

      const currentIndex = i;
      const task = tasks[i++];

      task()
        .then((res) => {
          results[currentIndex] = res;
        })
        .catch((err) => {
          results[currentIndex] = err;
        })
        .finally(() => {
          finished++;

          if (finished === tasks.length) {
            resolve(results);
          } else {
            run(); // 补位
          }
        });
    }

    for (let j = 0; j < limit; j++) {
      run();
    }
  });
}

执行流程

  1. 先执行 limit 个任务
  2. 任意任务完成 → 补一个新任务
  3. 循环直到完成

八、整体知识体系总结

三类问题及解法:

问题 解决方案
请求竞态 打标 / AbortController
并发请求 Promise.all / allSettled
并发过多 并发控制(limit)

九、面试总结话术

请求竞态

通过请求打标或 AbortController 控制请求生命周期,避免旧请求覆盖新数据。

AbortController

基于事件机制,通过 signal 通知浏览器网络层中断请求。

并发控制

通过维护执行池,控制最大并发数,并在任务完成后动态补位。


后续学习方向

下一步可以继续深入:

  • 并发控制 + 重试机制
  • 请求超时设计
  • 请求缓存(Cache)
  • React Hook 封装(useRequest)

总结

今天你已经掌握了一条非常重要的前端能力链路:

请求竞态 → 请求取消 → React 实战 → 并发理解 → 并发控制

这套知识在实际开发和面试中都非常有价值。

下一步建议:请求库封装(工程级 useRequest 实现),将本文所有知识整合成一个真实项目级方案。

相关推荐
葡萄城技术团队2 小时前
1000 人协同压测的环境瓶颈排查:CPU 争用与 Wi\-Fi
前端
excel3 小时前
Marked 与 Markdown 有什么区别?该怎么选?
前端
Da Da 泓4 小时前
五子棋 Web 端测试用例报告
前端·网络·websocket·网络协议·测试用例·项目
zxcvb1535 小时前
远程查看本地文档:一条轻量 Web 文件服务的工程化链路
前端·远程查看文档
一木 之林5 小时前
Dify学习笔记 03 · RAG检索增强生成:三代范式、评估与落地(第40-45、55-56集)
笔记·小程序·交互·dify
niucloud-admin5 小时前
NIUCLOUD PHP SAAS上门家政使用文档:城市管理
java·前端·php
北岛贰5 小时前
啵啵音乐迎来全新升级!
前端
apple-mapping5 小时前
网络RTK定位学习总结
学习·网络rtk
楚山之石6 小时前
【Lancet Neurology 】多发性硬化认知障碍的 SDMT 筛查、灰质萎缩标志与认知康复循证路径
图像处理·人工智能·学习·mri
hkNaruto7 小时前
【AI】基于大模型的AI游戏地图生成、控制方案:AI Native Game Harness 迁移操作手册
前端·人工智能·游戏