目录

复杂计算任务的智能轮询优化实战

一、轮询方法介绍

二、三种轮询优化策略

1、用 setTimeout 替代 setInterval

2、轮询时间指数退避

3、标签页可见性检测(Page Visibility API)

三、封装一个简单易用的智能轮询方法

四、结语


        作者:watermelo37

        CSDN全栈领域优质创作者、万粉博主、华为云云享专家、阿里云专家博主、腾讯云“创作之星”特邀作者、支付宝合作作者,全平台博客昵称watermelo37。

        一个假装是giser的coder,做不只专注于业务逻辑的前端工程师,Java、Docker、Python、LLM均有涉猎。

---------------------------------------------------------------------

温柔地对待温柔的人,包容的三观就是最大的温柔。

---------------------------------------------------------------------

复杂计算任务的智能轮询优化实战

一、轮询方法介绍

        在前端开发中,我们经常需要轮询后端任务状态,例如文件处理、报告生成、复杂计算等长时间任务。如果盲目使用 setInterval,不仅容易浪费资源,还可能造成性能问题。本文将分享一种结合三种策略的优化方案,显著降低轮询次数,同时保证用户体验。

        之前写过一个项目,有非常庞大的数据计算需求,经常会出现一个计算项目需要十几分钟,甚至几十分钟的情况(但是有缓存的时候又只需要几十秒),这个时候还是用传统的 setInterval 轮询策略显得太过浪费,占用大量系统资源的同时,也给服务器带来的一定的负荷,实在是过于笨重。

        您可以根据您的项目实际需求,选取若干种或者全部策略应用到您的项目中。请注意,只有在长时间复杂计算任务的轮询中,才会有极大的优化效果,短时间(几秒或者十几秒)的计算任务轮询本身就不占用太多资源,优化效果不佳。

        最重要的是面对甲方,这种优化非常有牌面,汇报的时候可以做做文章。

二、三种轮询优化策略

1、用 setTimeout 替代 setInterval

        传统轮询做法是这样的:

const intervalId = setInterval(async () => {const result = await checkTaskStatus();if (result.done) clearInterval(intervalId);
}, 2000);

        如果网络波动或者请求延迟,可能出现多次轮询重叠,造成额外请求和压力。而且这种基于本地时间的寻轮本身就是不符合实际情况的。

        优化做法:递归调用 setTimeout:

async function pollTask() {const result = await checkTaskStatus();if (!result.done) {setTimeout(pollTask, 2000);}
}pollTask();

        这样可以避免请求重叠,网络慢时自然延后下一次轮询,而且无需手动清除定时器,更加灵活。

2、轮询时间指数退避

        对于长时间任务,前几次轮询未返回结果,就意味着任务耗时较长,盲目频繁轮询没必要。指数退避可以动态增加轮询间隔,降低冗余请求:

let attempt = 0;
const baseInterval = 1000; // 初始间隔 1s
const maxInterval = 30000; // 最大间隔 30sasync function pollWithBackoff() {const result = await checkTaskStatus();if (!result.done) {attempt++;const interval = Math.min(baseInterval * 1.5 ** attempt, maxInterval);setTimeout(pollWithBackoff, interval);}
}pollWithBackoff();

        需要根据历史任务完成时间调整指数底数或最大间隔。

        这样的话前几次快速轮询,保证非长时间计算任务(比如计算量小或者有缓存记录)用户能尽早获取结果。随着尝试次数增加,轮询间隔指数增长,显著降低冗余请求。

3、标签页可见性检测(Page Visibility API)

        在长时间任务中,用户经常会选择将页面放到后台,这个时候可以通过 Page Visibility API 进一步调整轮询策略,降低不必要的CPU和网络资源占用。将轮询的时长进一步延长到一个极大值。如果用户将标签页放到前台,说明他想检查计算结果。那么次数如果间隔时间超过了指数退避应有的时间,但是没到后台间隔时间,会立即触发轮询,检查状态。

let isPolling = false;
let timeoutId = null;async function smartPoll() {if (isPolling) return; // 已经有轮询在进行,不再触发isPolling = true;const result = await checkTaskStatus();isPolling = false;if (!result.done) {let nextInterval;if (!isPageVisible) {nextInterval = 60000; // 后台最大延迟} else {nextInterval = Math.min(baseInterval * 2 ** attempt, maxInterval);}timeoutId = setTimeout(smartPoll, nextInterval);}
}// 用户回到前台时立即触发
document.addEventListener("visibilitychange", () => {isPageVisible = !document.hidden;if (isPageVisible) {// 取消原来的定时器,立即轮询if (timeoutId) clearTimeout(timeoutId);smartPoll();}
});smartPoll();

        这样可以在用户不关注时,降低轮询频率,节省资源,用户切回前台时,立即按照指数退避策略或更短间隔继续轮询,保证响应及时。

三、封装一个简单易用的智能轮询方法

        上述三种方式叠加起来,就可以封装成一个非常优秀的长时间复杂计算任务轮询的优化算法,具有如下优势:

  • 自适应退避策略

        前台轮询:采用指数退避(baseInterval * 2^attempt),避免短时间内重复请求。

        后台轮询:固定间隔 maxBackoff,节省 CPU 和网络资源。

  • 抖动机制(Jitter)

        在计算间隔时加入随机抖动(±jitterRatio),避免大量客户端同时请求导致“雪崩效应”。

  • 可见性感知

        页面切换到后台时自动延长轮询间隔,页面切回前台时会立即触发一次抢跑,保证用户看到最新数据。

  • 错误处理与自动重试

        异步任务出错时,前台依然采用指数退避,后台采用固定间隔重试,任务出错不会中断整个轮询流程。

  • 任务取消支持

        每轮任务都会传入新的 AbortSignal,调用 poller.stop() 或新任务启动时可中止上一轮任务。

  • 服务端可控间隔

        如果任务返回 retryAfter,会优先采用服务器建议的轮询间隔,同时重置指数退避。

  • 封装完善,简洁易用

        启动轮询只需调用 createSmartPoll 并传入异步任务即可,提供 stop 方法一键停止轮询及清理资源。

        在实际开发中效果非常好:

/*** 智能轮询* @param {(signal: AbortSignal) => Promise<{ done: boolean, retryAfter?: number }>} taskFn* @param {Object} options*   baseInterval: 初始轮询间隔(ms),默认 1000*   maxInterval: 最大轮询间隔(ms),默认 30000*   maxBackoff: 页面隐藏时的固定间隔(ms),默认 60000*   jitter: 抖动系数(0~1),默认 0.2 表示 ±20%*/
function createSmartPoll(taskFn, options = {}) {const baseInterval = options.baseInterval ?? 1000;const maxInterval  = options.maxInterval  ?? 30000;const maxBackoff   = options.maxBackoff   ?? 60000;const jitterRatio  = options.jitter ?? 0.2;let attemptVisible = 0;      // 仅在「可见 + 未完成/出错」时增长let isPageVisible  = typeof document !== 'undefined' ? !document.hidden : true;let timeoutId      = null;let isPolling      = false;let stopped        = false;let controller     = new AbortController();const addJitter = (ms) => {if (!jitterRatio) return ms;const delta = ms * jitterRatio;return Math.max(0, ms + (Math.random() * 2 - 1) * delta);};const cleanup = () => {if (timeoutId) {clearTimeout(timeoutId);timeoutId = null;}if (typeof document !== 'undefined') {document.removeEventListener('visibilitychange', onVisibility);}controller.abort();stopped = true;};async function poll() {if (stopped || isPolling) return;isPolling = true;try {// 确保每轮都有自己的 abort 信号controller.abort(); // 取消上一轮遗留controller = new AbortController();const result = await taskFn(controller.signal);const done = !!(result && typeof result.done === 'boolean' ? result.done : false);if (done) {cleanup();return;}// 计算下一次间隔let interval;if (!isPageVisible) {// 后台固定间隔,并且不增长 attemptVisibleinterval = maxBackoff;} else if (result && typeof result.retryAfter === 'number') {// 服务器/任务建议的间隔优先interval = Math.max(0, Math.min(result.retryAfter, maxInterval));attemptVisible = 0; // 有明确指示时可视为“重置退避”} else {attemptVisible++;interval = Math.min(baseInterval * (1.5 ** attemptVisible), maxInterval);}interval = addJitter(interval);// 先清旧的,避免遗留定时器if (timeoutId) clearTimeout(timeoutId);timeoutId = setTimeout(() => {timeoutId = null;poll();}, interval);} catch (err) {console.error('轮询任务出错:', err);// 出错:可见时才增长退避;后台固定间隔let interval;if (!isPageVisible) {interval = maxBackoff;} else {attemptVisible++;interval = Math.min(baseInterval * (2 ** attemptVisible), maxInterval);}interval = addJitter(interval);if (timeoutId) clearTimeout(timeoutId);timeoutId = setTimeout(() => {timeoutId = null;poll();}, interval);} finally {isPolling = false;}}function onVisibility() {const nowVisible = !document.hidden;if (nowVisible === isPageVisible) return;isPageVisible = nowVisible;if (isPageVisible) {// 回到前台:无条件抢跑一次if (timeoutId) {clearTimeout(timeoutId);timeoutId = null;}// 若当前正在执行,等其结束;立即排一个 0ms 的下一轮timeoutId = setTimeout(() => {timeoutId = null;poll();}, 0);}}if (typeof document !== 'undefined') {document.addEventListener('visibilitychange', onVisibility);}// 启动poll();return {stop: cleanup};
}

        调用案例如下:

// 模拟异步任务
async function fetchData(signal) {// 这里用 fetch 举例,可传 signal 用于取消try {const response = await fetch('/api/status', { signal });const data = await response.json();// 假设服务器返回 { finished: boolean, nextCheck: number(ms) }return {done: data.finished,retryAfter: data.nextCheck // 可选};} catch (err) {if (err.name === 'AbortError') {console.log('任务被中止');} else {console.warn('请求错误:', err);}// 出错返回 done=false 表示继续轮询return { done: false };}
}// 创建智能轮询实例
const poller = createSmartPoll(fetchData, {baseInterval: 1000,   // 初始 1 秒maxInterval: 10000,   // 最大 10 秒maxBackoff: 30000,    // 后台固定 30 秒jitter: 0.2           // ±20% 抖动
});// 停止轮询示例
setTimeout(() => {poller.stop();console.log('轮询已停止');
}, 60000); // 1 分钟后停止

四、结语

通过以下三种策略的叠加,可以极大优化长时间任务轮询:

  1. 递归 setTimeout:避免轮询叠加,更灵活。

  2. 指数退避:减少长任务中无效轮询。

  3. 标签页可见性检测:降低后台页面的资源消耗。

        实践中,这三种策略结合起来,既保证了用户体验,又大幅降低了服务器压力。未来可结合任务分布数据和智能算法进一步优化轮询策略,实现真正的“智能轮询”。

        只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~

        其他热门文章,请关注:

        极致的灵活度满足工程美学:用Vue Flow绘制一个完美流程图

        你真的会使用Vue3的onMounted钩子函数吗?Vue3中onMounted的用法详解

        Web Worker:让前端飞起来的隐形引擎

        DeepSeek:全栈开发者视角下的AI革命者

        通过array.filter()实现数组的数据筛选、数据清洗和链式调用

        测评:这B班上的值不值?在不同城市过上同等生活水平到底需要多少钱?

        通过Array.sort() 实现多字段排序、排序稳定性、随机排序洗牌算法、优化排序性能

        TreeSize:免费的磁盘清理与管理神器,解决C盘爆满的燃眉之急

        通过MongoDB Atlas 实现语义搜索与 RAG——迈向AI的搜索机制

        深入理解 JavaScript 中的 Array.find() 方法:原理、性能优势与实用案例详解

        前端实战:基于Vue3与免费满血版DeepSeek实现无限滚动+懒加载+瀑布流模块及优化策略

        el-table实现动态数据的实时排序,一篇文章讲清楚elementui的表格排序功能

        JavaScript双问号操作符(??)详解,解决使用 || 时因类型转换带来的问题

      【前端实战】如何让用户回到上次阅读的位置?

        内存泄漏——海量数据背后隐藏的项目生产环境崩溃风险!如何避免内存泄漏

        MutationObserver详解+案例——深入理解 JavaScript 中的 MutationObserver

        高效工作流:用Mermaid绘制你的专属流程图;如何在Vue3中导入mermaid绘制流程图

        JavaScript中通过array.map()实现数据转换、创建派生数组、异步数据流处理、DOM操作等

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/98400.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/98400.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/98400.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java开发中常用CollectionUtils方式,以及Spring中CollectionUtils常用方法示例

场景 Java开发中常用的CollectionUtils 一、Spring Framework的CollectionUtils 包路径&#xff1a;org.springframework.util.CollectionUtils 核心方法&#xff1a; isEmpty(Collection<?> coll) List<String> list null; boolean empty CollectionUtil…

人工智能学习:Transformer结构(文本嵌入及其位置编码器)

一、输入部分介绍 输入部分包含: 编码器源文本嵌入层及其位置编码器 解码器目标文本嵌入层及其位置编码器 在transformer的encoder和decoder的输入层中,使用了Positional Encoding,使得最终的输入满足: 这里,input_embedding是通过常规embedding层,将每一个词的…

⸢ 肆 ⸥ ⤳ 默认安全建设方案:c-1.增量风险管控

&#x1f44d;点「赞」&#x1f4cc;收「藏」&#x1f440;关「注」&#x1f4ac;评「论」 在金融科技深度融合的背景下&#xff0c;信息安全已从单纯的技术攻防扩展至架构、合规、流程与创新的系统工程。作为一名从业十多年的老兵&#xff0c;将系统阐述数字银行安全体系的建设…

第二课、熟悉Cocos Creator 编辑器界面

本文主要介绍Cocos Creator 编辑器界面中几个常规的面板功能&#xff0c;让新手了解编辑器界面中常规的面板功能&#xff0c;更好的使用Cocos Creator 编辑器。一、编辑器界面常规面板划分Cocos Creater编辑器默认样式如上&#xff0c;主要包含&#xff1a;1、工具栏&#xff0…

Elixir通过Onvif协议控制IP摄像机,扩展ExOnvif的摄像头连续移动功能 ContinuousMove

Elixir 通过Onvif 对IP设备进行控制时&#xff0c;可以使用 ExOnvif 库。ExOnvif官方文档 此文章仅提供了ContinuousMove的控制方式及示例。 Elixir Onvif协议控制IP设备的其他命令&#xff0c;可以参考以下链接 绝对移动 【AbsoluteMove】 调用指定预置位 【GotoPreset】 …

android studio JNI 环境配置实现 java 调用 c/c++

1、在 app 级的 build.gradle 文件配置两个地方 android{ defaultConfig{ // 在 defaultConfig 里配置下面代码 externalNativeBuild { cmake { cppFlags "-frtti -fexceptions"//添加对 c 的异常处理支持 …

静态时序分析详解之时序路径类型

目录 一、概览 二、时序路径 2.1 数据路径 2.2 时钟路径 2.3 时钟门控路径 2.4 异步路径 2.5 关键路径 2.6 False路径 2.7 单周期路径 2.8 多周期路径 2.9 最长路径和最短路径 三、参考资料 一、概览 ​ ​静态时序分析通过模拟最差条件下分析所有的时序路径&am…

SpringBoot埋点功能技术实现方案深度解析:架构设计、性能优化与扩展性实践

SpringBoot埋点功能技术实现方案深度解析&#xff1a;架构设计、性能优化与扩展性实践 1. 原理剖析与技术实现细节 1.1 埋点技术基本原理 埋点&#xff08;Tracking&#xff09;是通过在代码中植入特定逻辑&#xff0c;收集用户行为数据、系统运行状态和业务指标的技术手段。在…

自建prometheus监控腾讯云k8s集群

自建prometheus监控腾讯云k8s集群 使用场景 k8s集群&#xff08;腾讯云容器服务&#xff09; promtheus (外部自建服务) 腾讯云提供了容器内部自建 Prometheus 监控 TKE 集群的文档&#xff0c;参考。 当前的环境promethues建在k8S外的云服务器上&#xff0c;与上面链接文…

2025高教社国赛数学建模C题参考论文(含模型和代码)

2025 年高教社杯大学生数学建模竞赛 C 题参考论文 目录 NIPT 的时点选择与胎儿的异常判定 摘要 1 问题重述 2 问题分析 2.1 问题 1 分析 2.2 问题 2 分析 2.3 问题 3 分析 2.4 问题 4 分析 3 模型假设与符号定义 3.1 模型假设 4. 孕周在 10-25 周内检测有…

iOS开发环境搭建及打包流程

一、下载xcode 直接去苹果商店的appstore下载就行 二、clone项目 1.登录xcode苹果账号或对应代码仓库账号 2.clone项目 3.安装设备真机环境&#xff08;未安装过的话&#xff09; 三.安装cocoapods 1. 检查并更新 Ruby 环境 CocoaPods 是基于 Ruby 编写的&#xff0c;因此…

数据结构之链表(单向链表与双向链表)

一&#xff0c;链表描述链表是一种常见的重要的数据结构,是动态地进行存储分配的一种结构。常用于需存储的数据的数目无法事先确定。1.链表的一般结构链表的组成&#xff1a; 头指针&#xff1a;存放一个地址&#xff0c;该地址指向一个元素 结点&#xff1a;用户需要的实际数据…

从反向代理到负载均衡:Nginx + Tomcat 构建高可用Web服务架构

从反向代理到负载均衡&#xff1a;Nginx Tomcat 构建高可用Web服务架构 文章目录从反向代理到负载均衡&#xff1a;Nginx Tomcat 构建高可用Web服务架构一、基础铺垫&#xff1a;什么是反向代理&#xff1f;1.1 反向代理的核心原理1.2 Nginx反向代理实战配置步骤1&#xff1a…

Simulink中使用Test sequence单元测试

一、Tips 在对simulink模型进行Test sequence单元测试时&#xff0c;如果采取书写测试用例的话&#xff0c;有以下操作。 1、使用”fprintf(‘time%f\n’, t);“来打印当前step的时间&#xff1b; 二、数据类型转换 1、double类型 -> boolean类型 clc; clear all;% 1、doubl…

【mysql】SQL自连接:什么时候需要,什么时候不需要?

SQL自连接:什么时候需要,什么时候不需要? 通过具体示例和对比解析,彻底搞懂SQL自连接的使用场景 在处理SQL查询时,尤其是当表中存在自引用关系(如referee_id引用同一张表的id)时,很多开发者会疑惑:这个查询到底需不需要自连接?本文将通过多个具体示例,带你彻底弄清何…

「美」创新在于人,而不是产品 - AxureMost 落葵网

添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 第一章&#xff1a;创新的心理学 创新与心理安全 蜡烛问题&#xff1a;卡尔邓克尔的蜡烛问题实验揭示了创造性思维的重要性。通过颠覆对盒子用途的先入为主观念&#xff0c;参与者能够找到创新性的解决方案…

新规则,新游戏:AI时代下的战略重构与商业实践

当你的客服AI能够真正像员工一样理解客户的行业术语&#xff0c;当AI能主动从大量的客户咨询中筛选出高价值潜在客户 —— 这已经不再是理想中才能存在的场景&#xff0c;而是当下 “人工智能 ” 行动深入推进中&#xff0c;企业智能化转型的真实写照。 "人工智能 " …

ScanNet: Richly-annotated 3D Reconstructions of Indoor Scenes 数据集构建

paper link: paperlink Abstract: 这个数据集是个RGB-D视频数据集&#xff0c;在707个不同空间中获取了1513个扫描的场景&#xff0c;250w个视图&#xff0c;并且标注了相机位姿&#xff0c;表面重建&#xff0c;语义分割。本数据集共有20人扫描500名工作者进行标注。 数据集…

c语言期末复习

一、选择题(10道) 1、以下哪个不是C语言的关键字? A) int B) float C) string D) while (答案:C) 2、表达式 5 / 2 的结果是: A) 2.5 B) 2 C) 3 D) 2.0 (答案:B) 3、指针变量存储的是: A) 变量的值 B) 变量的地址 C) 变量的类型 D) 变量的名称 (答案:B) 4、以…

JLINK 调试器单步调试单片机

0 JLINK 调试器单步调试单片机 1 物理层1.1 调整电压和开发板一致2 环境搭建 2.1 安装 JLink_Windows_V862_x86_642.2 vscode 配置 {"version": "0.2.0","configurations": [{"name": "(gdb) 启动","type": "…