☰
web网络请求竞态问题与取消方案-东方仙盟
2026/10/25 1:54:55 网站建设 项目流程

一、前言

在前端项目开发中,我们都会基于原生 XHR 二次封装myRequest通用网络请求方法,替代繁琐的原生写法,统一项目的请求头、超时时间、参数处理、成功 / 失败回调逻辑。

绝大多数普通业务场景(详情查询、新增、修改、静态数据加载)中,一次性请求、请求结束即销毁的逻辑完全够用,因此很多封装版本默认不支持请求取消功能。

但在筛选、搜索、Tab 切换、滚动加载、高频切换视图等交互场景下,网络请求无序返回的竞态 Bug 会频繁出现,而原生封装的 myRequest 不具备取消能力,会直接导致页面数据错乱、状态闪烁、接口冗余请求、前端渲染异常等一系列问题。

本文专门针对自研 myRequest 封装,讲解为什么要做请求取消、如何向下百分百兼容老项目、如何极简实现可取消请求,彻底解决请求竞态问题。

二、为什么 myRequest 必须支持「请求取消」

1. 核心元凶:网络请求竞态(Race Condition)

前端网络请求是异步、非阻塞的,我们代码的执行顺序,不等于接口响应的返回顺序。

举个典型业务场景:搜索框实时联想

  1. 用户输入1→ 发起请求 1
  2. 用户快速输入12→ 发起请求 2
  3. 用户快速输入123→ 发起请求 3

代码执行顺序:请求 1 → 请求 2 → 请求 3但网络延迟不可控,极易出现响应顺序:请求 3(快)→ 请求 2(中)→ 请求 1(最慢)

2. 无取消能力导致的致命 Bug

  • 最新的请求 3 数据先渲染页面,界面正常展示;
  • 几秒后,延迟的旧请求 2、请求 1 陆续返回;
  • 旧数据覆盖最新数据,页面莫名回退、数据错乱、闪屏;

这是前端最经典、最高频、最难排查的隐性 Bug。

3. 传统 myRequest 封装的设计缺陷

我们常规的 myRequest 封装逻辑:

  • 函数内部创建 XHR 实例;
  • 通过 success /error 回调返回结果;
  • 函数无任何返回值,外部无法获取 XHR 实例引用;

这就导致一个致命问题:请求发出去之后,彻底 “失控”,外部没有任何手段终止正在 pending 的网络请求,只能被动等待所有请求依次返回,任由旧数据污染页面。

4. 为什么不推荐用 “定时器 / 防抖” 替代取消?

很多开发者会用防抖来减少请求次数,但防抖只能减少请求发起数量,无法根治竞态: 就算间隔 300ms 防抖,依旧存在网络延迟差异,依旧会出现旧请求滞后覆盖新数据的问题。

结论:防抖是优化,请求取消是根治,二者必须搭配使用。

三、核心设计理念:兼容老项目,按需开启取消能力

我们改造的核心原则:

  1. 老代码零改动:原有所有通过 success /error 回调的业务,完全不受影响;
  2. 按需使用能力:不需要取消请求,就不接收返回值,照常使用;需要防竞态的场景,接收句柄、手动取消;
  3. 不破坏原有封装逻辑:不改参数、不改回调、不改变项目使用习惯。

实现核心逻辑:myRequest 函数新增返回「请求控制器句柄」,普通业务无视返回值,高频交互业务接收句柄,实现手动取消。

四、可直接上线的 myRequest 最终封装(支持取消)

完整封装代码

/** * 自定义通用网络请求 myRequest * 支持:常规回调使用 + 按需取消请求 * 兼容所有老项目写法,零侵入 */ function myRequest(options) { // 默认配置 const config = { method: "GET", url: "", data: null, timeout: 10000, success: null, error: null, ...options }; const xhr = new XMLHttpRequest(); let isCancel = false; // 手动取消标记 // 【核心】对外暴露的请求控制器 const requestHandle = { // 取消当前请求 cancel: function () { isCancel = true; xhr.abort(); } }; // 请求方式、地址配置 if (config.method.toUpperCase() === "GET" && config.data) { // GET参数拼接 const params = new URLSearchParams(config.data); config.url += (config.url.includes("?") ? "&" : "?") + params; } xhr.open(config.method.toUpperCase(), config.url, true); xhr.timeout = config.timeout; // 统一请求头 xhr.setRequestHeader("Content-Type", "application/json;charset=utf-8"); // 请求成功回调 xhr.onload = function () { // 如果是手动取消,直接拦截,不执行任何回调 if (isCancel) return; try { const res = JSON.parse(xhr.responseText); config.success && config.success(res); } catch (err) { config.error && config.error("数据解析失败"); } }; // 请求超时/网络错误 xhr.onerror = xhr.ontimeout = function () { if (isCancel) return; config.error && config.error("网络请求异常"); }; // 发送请求 const sendData = config.method.toUpperCase() === "POST" ? JSON.stringify(config.data) : null; xhr.send(sendData); // 关键:返回控制器句柄 return requestHandle; }

五、两种使用方式(完美兼容新旧业务)

方式一:普通业务(无需取消,老代码原样不动)

绝大多数静态查询、新增、修改业务,完全不用改代码:

// 传统写法,零改动、零兼容问题 myRequest({ url: "/api/user/info", method: "GET", success: (res) => { console.log("请求成功", res); }, error: (err) => { console.log("请求失败", err); } });

方式二:高频交互业务(需要取消,防竞态)

搜索、Tab 切换、筛选等场景,接收句柄,发新请求取消旧请求:

// 全局保存当前场景的请求句柄 let searchRequest = null; // 搜索查询函数 function searchList(keyword) { // 【核心】发起新请求前,取消上一次未完成的请求 if (searchRequest) { searchRequest.cancel(); } // 接收本次请求的控制器 searchRequest = myRequest({ url: "/api/goods/search", method: "GET", data: { key: keyword }, success: (res) => { // 永远只渲染最新请求的数据 console.log("最新搜索结果", res); } }); }

六、关键细节与避坑说明

1. 手动取消不会触发 error 报错

代码中通过isCancel标记拦截了取消后的回调,取消请求不会弹出错误提示,和 jQuery Ajax 行为一致,用户体验最优。

2. 句柄独立管理,互不干扰

不同业务场景必须单独定义句柄变量:

  • 搜索:searchRequest
  • Tab 切换:tabRequest
  • 列表筛选:filterRequest

避免多个业务共用一个句柄,导致误取消其他请求。

3. 前端取消 ≠ 后端终止

xhr.abort()只会关闭浏览器的响应接收,后端接口已经触发,后台逻辑会正常执行。 如果涉及支付、库存、数据写入等高危操作,不能依赖前端取消,需后端做幂等、序列号校验。

七、总结

  1. 前端数据错乱、闪屏的核心根源是异步请求竞态,防抖只能优化,请求取消才能根治;
  2. 自研myRequest无需重构,只需返回控制器句柄,即可低成本拥有取消能力;
  3. 改造完全向下兼容,老业务零改动,新业务按需启用取消功能;
  4. 核心逻辑:同场景发新请求 → 取消未完成的旧请求 → 保证永远只渲染最新数据。

人人皆为创造者,共创方能共成长

每个人都是使用者,也是创造者;是数字世界的消费者,更是价值的生产者与分享者。在智能时代的浪潮里,单打独斗的发展模式早已落幕,唯有开放连接、创意共创、利益共享,才能让个体价值汇聚成生态合力,让技术与创意双向奔赴,实现平台与伙伴的快速成长、共赢致远。

原创永久分成,共赴星辰大海


原创创意共创、永久收益分成,是东方仙盟始终坚守的核心理念。我们坚信,每一份原创智慧都值得被尊重与回馈,以永久分成锚定共创初心,让创意者长期享有价值红利,携手万千伙伴向着科技星辰大海笃定前行,拥抱硅基 生命与数字智能交融的未来,共筑跨越时代的数字文明共同体。

东方仙盟:拥抱知识开源,共筑数字新生态


在全球化与数字化浪潮中,东方仙盟始终秉持开放协作、知识共享的理念,积极拥抱开源技术与开放标准。我们相信,唯有打破技术壁垒、汇聚全球智慧,才能真正推动行业的可持续发展。

开源赋能中小商户:通过将前端异常检测、跨系统数据互联等核心能力开源化,东方仙盟为全球中小商户提供了低成本、高可靠的技术解决方案,让更多商家能够平等享受数字转型的红利。
共建行业标准:我们积极参与国际技术社区,与全球开发者、合作伙伴共同制定开放协议 与技术规范,推动跨境零售、文旅、餐饮等多业态的系统互联互通,构建更加公平、高效的数字生态。
知识普惠,共促发展:通过开源社区 、技术文档与培训体系,东方仙盟致力于将前沿技术转化为可落地的行业实践,赋能全球合作伙伴,共同培育创新人才,推动数字经济 的普惠式增长


阿雪技术观

在科技发展浪潮中,我们不妨积极投身技术共享。不满足于做受益者,更要主动担当贡献者 。无论是分享代码、撰写技术博客,还是参与开源项目 维护改进,每一个微小举动都可能蕴含推动技术进步的巨大能量。东方仙盟是汇聚力量的天地,我们携手在此探索硅基 生命,为科技进步添砖加瓦。

Hey folks, in this wild tech - driven world, why not dive headfirst into the whole tech - sharing scene? Don't just be the one reaping all the benefits; step up and be a contributor too. Whether you're tossing out your code snippets , hammering out some tech blogs, or getting your hands dirty with maintaining and sprucing up open - source projects, every little thing you do might just end up being a massive force that pushes tech forward. And guess what? The Eastern FairyAlliance is this awesome place where we all come together. We're gonna team up and explore the whole silicon - based life thing, and in the process, we'll be fueling the growth of technology

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询