setInterval 定时任务执行时间不准验证
一般在处理定时任务的时候都使用setInterval间隔定时调用任务。
setInterval(() => {console.log("interval");
}, 2 * 1000);
我们定义的是两秒执行一次,但是浏览器实际执行的间隔时间只多不少。这是由于浏览器执行 JS 是单线程模式,使用setInterval定时执行的回调只会在线程空闲时调用。
通过增加时间记录,对比每次调用的间隔并打印:
// 记录最后一次调用的时间
let lastCall = Date.now();setInterval(() => {let now = Date.now();// 由最后一次调用时间和当前时间计算出间隔时间let delay = now - lastCall;lastCall = now;console.log("interval---", delay);
}, 2 * 1000);

看着执行间隔时间还行,没差多少。因为我们的测试页面什么都没有,主线程一直空着,没有被影响.
我们来增加一个按钮,然后点击事件后随机生成数字,然后排序。目的是为了占用主线程,看定时任务的执行时间
function handleClick(event) {let arr = [];for (let i = 0; i < 1000000; i++) {let num = Math.random() * 10000;arr.push(num);}arr.sort();console.log("-------click---------");
}
本来是几千、几万条数据进行测试的,发现执行速度很快,不能长时间占用。就直接加大数据数量,然后连续点击按钮十几下。
可以看看对比效果:

可以看到我连续点击十多次,导致主线程一直被占用。主线程空闲后执行定时任务,第一个任务执行时间由 10ms,第二任务执行只有 1.3 秒。这是为什么呢?
因为setInterval的执行不在乎主线程有没有空,它只会按照间隔触发函数执行,而这个回调任务会被加入到任务队列中。等待主线程空闲时出队列调用。
大于间隔时间是主线程被占用,任务等待执行,导致整个时间超了;小于间隔时间是线程占用时间过长,任务执行队列中已经存在多个等待执行的任务,导致上一个任务刚执行完,下一个任务就执行了。
那么我们定时任务就不能按照间隔正常调用,因为我们无法改变 JS 单线程的事实。
但可以解决一下间隔时间小于指定的时间间隔的问题。也就是每次执行回调时间都尽可能的>=指定的时间间隔。
setTimeout
使用setTimeout实现一个自定义循环,在循环每次结束后,重新设置一个定时器,而不是预先固定间隔。
let lastCall = Date.now();
function intervalCall() {let now = Date.now();let delay = now - lastCall;lastCall = now;console.log("interval---", delay);setTimeout(intervalCall, 2 * 1000);
}
intervalCall();
测试,可以看到在主线程空闲之后的两次任务调用中,第一个任务执行超过 10 秒,第二个任务 2 秒多。这就解决了间隔执行时间小于指定时间间隔的问题。

补偿执行时间
什么叫执行时间,就是你的回调业务逻辑执行的时间,我们之前验证了间隔时间不准确的问题,这个没法解决。但可以考虑优化调整一下下次任务执行时的间隔。
如果回调业务逻辑里很复杂,很耗时,那执行到最后时重置的定时器执行间隔已经不准了。
let lastCall = Date.now();
function intervalCall() {let now = Date.now();let delay = now - lastCall;lastCall = now;console.log("interval---", delay);// 耗时let arr = [];for (let i = 0; i < 1000000; i++) {let num = Math.random() * 10000;arr.push(num);}arr.sort();setTimeout(intervalCall, 2 * 1000);
}
intervalCall();
可以看到由于耗时的任务,导致每次的间隔调用都在 3、4 秒了,所以这部分执行时间我们要补偿回来。

function intervalCall() {let now = Date.now();//... 业务逻辑// 执行结束时间let handlerTime = now - Date.now();// 下一次的间隔时间let intervalTime = Math.max(0, 20000 - handlerTime);setTimeout(intervalCall, intervalTime);
}
我们在执行开始记录了执行的开始时间now,在业务逻辑执行完后记录执行完毕的时间handlerTime,然后计算出执行时间,并在下次定时中减掉执行时间。但是有可能出现执行时间大于函数执行间隔时间,所以Math.max(0, 20000 - handlerTime),最短间隔 0m,直接执行。
可以看到测试数据,比没处理完好很多,基本都在 2 秒多一点。

上面是使用了setTimeout进行时间补偿,那使用setInterval呢,使用setInterval后任务肯定是定时去调用回调的,会出现之前主线程被占用,导致任务队列中存在多个定时任务,主线程空闲后,直接执行的话两个任务之间的间隔就不足设定的间隔了。
let lastCall = Date.now();
setInterval(() => {let now = Date.now();let delay = now - lastCall;if (delay < 2000) {let intervalTime = 2000 - delay;setTimeout(() => {let now = Date.now();let delay = now - lastCall;console.log("补偿interval---", delay);lastCall = now;}, intervalTime);return;}console.log("interval---", delay);lastCall = now;
}, 2 * 1000);
计算了间隔时间delay,如果间隔时间还未到设定时间,则重新定制一个定时器setTimeout来执行任务。
setInterval不需要考虑任务执行时间,本身就是按照间隔时间去执行的。
重新执行测试主线程被占用时,后续任务执行情况。可以看到主线程被占用的第二个回调任务和第一个任务执行间隔在 2 秒多,不会少于间隔时间。这也尽可能保证按照设定间隔执行任务。

requestAnimationFrame 浏览器重绘之前执行
接受一个回调方法,在浏览器重绘之前调用一次。回调函数执行次数通常是每秒 60 次,它与浏览器屏幕刷新次数相匹配;在后台标签页或隐藏的 iframe 中,会停止执行。时间上可能会比较精确一点。
let lastCall = Date.now();function intervalCall() {let now = Date.now();let delay = now - lastCall;if (delay >= 2000) {// ...console.log("interval---", delay);lastCall = now;}requestAnimationFrame(intervalCall);
}
requestAnimationFrame(intervalCall);
在我点击按钮占用主线程时,居然见缝插针执行了一个任务。看来每秒 60 次的调用中还是很快的。

performance.now()精确度可达微秒级
改变Date.now使用performance.now来计算间隔时间
// let lastCall = Date.now();
let lastCall = performance.now();function intervalCall() {// let now = Date.now();let now = performance.now();let delay = now - lastCall;// console.log(now, "----", delay);if (delay >= 2000) {// ...console.log("interval---", delay);lastCall = now;}requestAnimationFrame(intervalCall);
}
requestAnimationFrame(intervalCall);
但是由于安全问题,这个 API 可能会跟浏览器的设置而废弃。实际上并不是高精度的,为了防范定时攻击和对指纹的保护,降低了原来的高精度。
优化耗时任务
上面测试了因为时间不准都是因为任务执行耗时,导致主线程被占用。从而加大了延时调用的时间,那么可以从优化执行耗时任务探索,尽可能的加快任务执行。
- 避免昂贵的计算和 DOM 操作。
- 使用
Web Workers,在后台线程中处理任务 - 对于一些操作,可以使用节流、防抖来限制在指定时间触发一次。
- 使用服务端定时器。
- 界面状态反馈。
- 减少页面负载,减少其他脚本和样式的加载时间。
使用Web Workers
在后台线程中处理任务,以免阻塞主线程。
如何使用web worker查看另一篇文章
提高执行效率
减少业务的执行时间,从优化代码、优化算法入手。还可以采用WebAssembly编码,它可以接近原生的性能运行。
它为诸如C \ C++ \ Rust等语言提供编译目标。
可以查看文章webAssembly 学习及使用 rust
通过文章基本了解 rust 是如何编译成WebAssembly,并在浏览器中运行的。比如在之前的测试代码中使用了sort排序来加长了任务的执行时间,如果采用 rust 编译的库提供的sort函数,则可以提升好几倍的执行速度。
测试示例代码仓库 - wasm-app
相关文章:
setInterval 定时任务执行时间不准验证
一般在处理定时任务的时候都使用setInterval间隔定时调用任务。 setInterval(() > {console.log("interval"); }, 2 * 1000);我们定义的是两秒执行一次,但是浏览器实际执行的间隔时间只多不少。这是由于浏览器执行 JS 是单线程模式,使用se…...
Stable Diffusion Model网站
Civitai Models | Discover Free Stable Diffusion Modelshttps://www.tjsky.net/tutorial/488https://zhuanlan.zhihu.com/p/610298913超详细的 Stable Diffusion ComfyUI 基础教程(一):安装与常用插件 - 优设网 - 学设计上优设 (uisdc.com)…...
K8S - 实现statefulset 有状态service的灰度发布
什么是灰度发布 Canary Release 参考 理解 什么是 滚动更新,蓝绿部署,灰度发布 以及它们的区别 配置partition in updateStrategy/rollingUpdate 这次我为修改了 statefulset 的1个yaml file statefulsets/stateful-nginx-without-pvc.yaml: --- apiVe…...
Qt 技术博客:深入理解 Qt 中的 delete 和 deleteLater 与信号槽机制
在 Qt 开发中,内存管理和对象生命周期的处理是至关重要的一环。特别是在涉及信号和槽机制时,如何正确删除对象会直接影响应用程序的稳定性。本文将详细讨论在使用 Qt 的信号和槽机制时,delete 和 deleteLater 的工作原理,并给出最…...
自学鸿蒙HarmonyOS的ArkTS语言<一>基本语法
一、一个ArkTs的目录结构 二、一个页面的结构 A、装饰器 Entry 装饰器 : 标记组件为入口组件,一个页面由多个自定义组件组成,但是只能有一个组件被标记 Component : 自定义组件, 仅能装饰struct关键字声明的数据结构 State:组件中的状态变量…...
【OpenGauss源码学习 —— (ALTER TABLE(列存修改列类型))】
ALTER TABLE(列存修改列类型) ATExecAlterColumnType 函数1. 检查和处理列存储表的字符集:2. 处理自动递增列的数据类型检查:3. 处理生成列的类型转换检查:4. 处理生成列的数据类型转换: build_column_defa…...
【大数据 复习】第7章 MapReduce(重中之重)
一、概念 1.MapReduce 设计就是“计算向数据靠拢”,而不是“数据向计算靠拢”,因为移动,数据需要大量的网络传输开销。 2.Hadoop MapReduce是分布式并行编程模型MapReduce的开源实现。 3.特点 (1)非共享式,…...
Zookeeper:节点
文章目录 一、节点类型二、监听器及节点删除三、创建节点四、监听节点变化五、判断节点是否存在 一、节点类型 持久(Persistent):客户端和服务器端断开连接后,创建的节点不删除。 持久化目录节点:客户端与Zookeeper断…...
生产级别的 vue
生产级别的 vue 拆分组件的标识更好的组织你的目录如何解决 props-base 设计的问题transparent component (透明组件)可减缓上述问题provide 和 inject vue-meta 在路由中的使用如何确保用户导航到某个路由自己都重新渲染?测试最佳实践如何制…...
kafka(五)spring-kafka(1)集成方法
一、集成 1、pom依赖 <!--kafka--><dependency><groupId>org.apache.kafka</groupId><artifactId>kafka-clients</artifactId></dependency><dependency><groupId>org.springframework.kafka</groupId><artif…...
Java中的设计模式深度解析
Java中的设计模式深度解析 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿! 在软件开发领域,设计模式是一种被广泛应用的经验总结和解决方案&#x…...
鸿蒙 HarmonyOS NEXT星河版APP应用开发—上篇
一、鸿蒙开发环境搭建 DevEco Studio安装 下载 访问官网:https://developer.huawei.com/consumer/cn/deveco-studio/选择操作系统版本后并注册登录华为账号既可下载安装包 安装 建议:软件和依赖安装目录不要使用中文字符软件安装包下载完成后࿰…...
[FreeRTOS 基础知识] 互斥访问与回环队列 概念
文章目录 为什么需要互斥访问?使用队列实现互斥访问休眠和唤醒机制环形缓冲区 为什么需要互斥访问? 在裸机中,假设有两个函数(func_A, func_B)都要修改a的值(a),那么将a定义为全局变…...
音视频的Buffer处理
最近在做安卓下UVC的一个案子。正好之前搞过ST方案的开机广告,这个也是我少数最后没搞成功的项目。当时也有点客观原因,当时ST要退出机顶盒市场,所以一切的支持都停了,当时啃他家播放器几十万行的代码,而且几乎没有文档…...
【总结】攻击 AI 模型的方法
数据投毒 污染训练数据 后门攻击 通过设计隐蔽的触发器,使得模型在正常测试时无异常,而面对触发器样本时被操纵输出。后门攻击可以看作是特殊的数据投毒,但是也可以通过修改模型参数来实现 对抗样本 只对输入做微小的改动,使模型…...
Linux配置中文环境
文章目录 前言中文语言包中文输入法中文字体 前言 在Linux系统中修改为中文环境,通常涉及以下几个步骤: 中文语言包 更新源列表: 更新系统的软件源列表和语言环境设置,确保可以安装所需的语言包。 sudo apt update sudo apt ins…...
深入解析 iOS 应用启动过程:main() 函数前的四大步骤
深入解析 iOS 应用启动过程:main() 函数前的四大步骤 背景描述:使用 Objective-C 开发的 iOS 或者 MacOS 应用 在开发 iOS 应用时,我们通常会关注 main() 函数及其之后的执行逻辑,但在 main() 函数之前,系统已经为我们…...
textarea标签改写为富文本框编辑器KindEditor
下载 - KindEditor - 在线HTML编辑器 KindEditor的简单使用-CSDN博客 一、 Maven需要的依赖: 如果依赖无法下载,可以多添加几个私服地址: 在Maven框架中加入镜像私服 <mirrors><!-- mirror| Specifies a repository mirror site to…...
高通安卓12-Input子系统
1.Input输入子系统架构 Input Driver(Input设备驱动层)->Input core(输入子系统核心层)->Event handler(事件处理层)->User space(用户空间) 2.getevent获取Input事件的用法 getevent 指令用于获取android系统中 input 输入事件,比如获取按键上报信息、获…...
HTML 事件
HTML 事件 HTML 事件是发生在 HTML 元素上的交互瞬间,它们可以由用户的行为(如点击、按键、鼠标移动等)或浏览器自身的行为(如页面加载完成、图片加载失败等)触发。在 HTML 和 JavaScript 的交互中,事件扮演着核心角色,允许开发者创建动态和响应式的网页。 常见的 HTM…...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...
SkyWalking 10.2.0 SWCK 配置过程
SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外,K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案,全安装在K8S群集中。 具体可参…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...
微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...
工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器
——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的一体化测试平台,覆盖应用全生命周期测试需求,主要提供五大核心能力: 测试类型检测目标关键指标功能体验基…...
Qt Http Server模块功能及架构
Qt Http Server 是 Qt 6.0 中引入的一个新模块,它提供了一个轻量级的 HTTP 服务器实现,主要用于构建基于 HTTP 的应用程序和服务。 功能介绍: 主要功能 HTTP服务器功能: 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...
关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
