当前位置: 首页 > ds >正文

setInterval可能的坑

1. setInterval会无视代码错误继续执行

        setInterval 会无视代码的错误。就算遇到了错误,它还是会一直循环下去,不会停止。这就导致了可能你代码里存在着一些问题(比如你的代码可能有个一定概率下会发生的错误,而你使用setinterval来循环调用它,由于setInterval 不会因为报错停止,所以这个问题可能被隐藏),可是却很难发现。

let count = 1;
setInterval(function () {count++;console.log(count);if (count % 3 === 0) throw new Error('setInterval报错');
}, 1000)

2. setInterval会无视任何情况下定时执行

        比如说,我们要实现一个功能,每隔一段时间要向服务器发送请求来查看是否有新数据。此时,若当时用户的网络状态很糟糕,客户端收到请求响应的时间大于interval循环的时间。而setInterval会无视任何情况下定时执行,这就会导致了用户的客户端里充斥着axios请求。这不仅会浪费网络资源,还可能导致服务器过载

2.1. 解决方法

       使用 setTimeout 替代 setInterval 来避免请求堆积,当用户发出去的请求得到响应或者超时后,再使用setTimeout 递归发送下一个请求。

2.2. 示例

        使用setInterval 时,若服务器响应时间超过 5 秒,新请求会不断发送,导致请求堆积。

setInterval(() => {fetch('https://api.example.com/check-new-data').then(response => response.json()).then(data => {console.log('新数据:', data);}).catch(error => {console.error('请求失败:', error);});
}, 5000);

        优化:使用 setTimeout 递归调用。

        每次请求完成后(无论是成功还是失败)再设置下一次请求。

const intervalTime = 5000; // 每次请求的间隔时间function fetchData() {fetch('https://api.example.com/check-new-data').then(response => response.json()).then(data => {console.log('新数据:', data);// 请求成功后,设置下一次请求setTimeout(fetchData, intervalTime);}).catch(error => {console.error('请求失败:', error);// 请求失败后,设置下一次请求setTimeout(fetchData, intervalTime);});
}// 启动第一次请求
fetchData();

3. setInterval可能会让内部函数不能完全调用

        setInterval 的间隔时长 < 小于内部调用函数的执行时长,可能会导致定时器内部的函数不能完全调用。

        如果说你的代码执行时间会比较久的话,就会导致setInterval 中的一部分函数调用被略过。因此程序如果依赖于setInterval 的精确执行的话,那么就要小心这一点了。

        当然,其实setTimeout也有这个问题。浏览器的定时器都不是精确执行的。就算你调用setTimeout(fn,0),它也不能确保马上执行。

4. setTimeout和setInterval都不能精确执行(时间不准)

        存在以下四种可能性会导致setTimeout/setInterval 无法精确执行

4.1. JS是单线程的

        JavaScript 是单线程的,意味着代码是顺序执行的。当调用 setTimeout时,它会将回调函数加入到事件队列中,等待当前线程空闲后执行。因此,如果当前线程有其他任务在执行(比如正在处理一个大的循环或复杂的操作),setTimeout的回调会延迟执行,从而导致超时不准确。

        基于上面的情况下,还有下面的另一种影响

        JavaScript 的执行是基于事件循环(Event Loop)的。setTimeout并不立即执行,而是将回调函数放入事件队列中,直到当前执行栈清空。所以即使设定了精确的延迟,回调的执行时间也可能因为事件循环的队列处理和任务调度的顺序而有所不同。

        也可以理解为设置setTimeout后的执行时间 = 执行栈清空的时间 + duration

4.2. 最小延迟时间

        在一些浏览器中,setTimeout的最小延迟时间可能并不是你设置的值。根据不同的环境(特别是在浏览器中),延迟时间可能会被浏览器限制,通常会有一个最小值,通常是 4 毫秒(但在一些浏览器中可能更长)。比如,如果你设置了 setTimeout(func, 1),可能实际上会延迟 4 毫秒或更长。

        当然也有另外的说法是在五级嵌套的情况下才会有这个四毫秒的延时。

4.3. 系统和硬件的影响

        不同操作系统和硬件的性能差异也会影响 setTimeout 的精确度。如果系统负载较高,或者操作系统有时间调度策略,setTimeout 可能会稍微延迟执行。

4.4. 失活页面

        失活页面(例如,用户切换到其他标签页或者浏览器处于后台时)会影响 setTimeout 和其他定时器的精度

        页面不再是活动页面时。浏览器会减少在后台标签页中执行 JavaScript 代码的频率,以节省资源和提升性能

        当页面处于非活动状态时,浏览器会延迟执行计时器回调,甚至可能会完全忽略一些定时器,直到页面恢复活动。

4.5. 解决方法

        

4.5.1. 使用递归 setTimeout模式+动态设置delay延迟时间

        为了避免因为系统延迟或事件队列的影响,使得每次 setTimeout 的实际延迟时间都略微偏离预期,可以通过setTimeout递归的方式动态调整下次的超时执行时间。也就是说,每次回调完成后,通过递归调用 setTimeout,同时根据当前时间动态调整下次的超时

function preciseTimeout(callback, interval) {const startTime = Date.now();  // 获取当前时间function loop() {const elapsedTime = Date.now() - startTime;  // 计算过去的时间const delay = Math.max(0, interval - (elapsedTime % interval));  // 计算下次的延迟时间setTimeout(function() {callback();loop();  // 递归调用,以保持精度}, delay);}loop();
}// 示例:每 100 毫秒执行一次任务
preciseTimeout(() => {console.log('执行任务', Date.now());
}, 100);

4.5.2. 使用requestAnimationFrame(可以用于高精度动画)

        使用 requestAnimationFrame 来替代 setTimeout

   requestAnimationFrame 会在浏览器的每一帧渲染之前调用,这样可以确保任务的执行和屏幕的刷新同步。

function preciseAnimation(callback) {let lastTime = 0;function animate(timestamp) {const deltaTime = timestamp - lastTime;  // 获取当前时间和上次执行的时间差if (deltaTime >= 100) {  // 每 100 毫秒执行一次callback();lastTime = timestamp;}requestAnimationFrame(animate);  // 请求下一帧}requestAnimationFrame(animate);
}// 示例:每 100 毫秒执行一次任务
preciseAnimation(() => {console.log('执行任务', Date.now());
});

        requestAnimationFrame 更适合用于动画和精确的时间控制,因为它会自动适应屏幕刷新率,通常是 60 FPS,因此会比setTimeout更平滑且精确。

        虽然requestAnimationFrame 不会收到事件循环的影响,也不受失活页面的影响,但是如果系统运行电脑卡了,会影响到渲染帧,其他页面的卡死也会影响到渲染帧,使得不精准。

4.5.3. 增加多次检查

        如果任务执行的时机非常重要,可以通过多次检查和校正延迟来提高精度。通过设定一个非常短的时间间隔并多次调整,可以使误差减少。

        例如,假设你每 10 毫秒检查一次时间,然后精确调整任务执行的时机:

function preciseCheck(callback, interval) {const targetTime = Date.now() + interval;  // 设定目标时间function check() {if (Date.now() >= targetTime) {callback();  // 执行任务} else {setTimeout(check, Math.max(0, targetTime - Date.now()));  // 调整等待时间}}check();
}// 示例:精确地每 100 毫秒执行一次任务
preciseCheck(() => {console.log('执行任务', Date.now());
}, 100);

        在这个方法中,我们通过每次检查是否已经达到目标时间,并调整下次检查的超时,来确保任务尽量按预期执行。适用于需要高精度控制时间间隔的任务,特别是对于不太依赖 CPU 密集型操作的任务

4.5.4. 使用Web Workers

        在主线程忙碌的时候,多开一个线程来执行计时,不会受到渲染帧和主线程的影响,是比较适合的方案。

// 在主线程中创建一个 Worker
const worker = new Worker('worker.js');// 在 Worker 中执行复杂任务
worker.postMessage('start');worker.onmessage = function(e) {console.log('Worker 回传消息:', e.data);
};// worker.js
onmessage = function() {// 执行复杂的计算或任务setInterval(() => {postMessage('任务完成');}, 100);  // 使用 setInterval,Web Worker 中执行任务不会阻塞主线程
};

http://www.xdnf.cn/news/1364.html

相关文章:

  • SSH 私钥文件权限控制指南
  • CLIMB自举框架:基于语义聚类的迭代数据混合优化及其在LLM预训练中的应用
  • Kotlin高阶函数 vs Lambda表达式:关键区别与协作关系
  • 用高斯溅射技术跨越机器人模拟与现实的鸿沟:SplatSim 框架解析
  • 通过 API 对接应用网络商城实现订单自动化
  • Prompt 结构化提示工程
  • flutter和vue3项目利用webview_flutter插件通信
  • C语言中的递归1.0
  • 在C#串口通信中,一发一收的场景,如何处理不同功能码的帧数据比较合理,代码结构好
  • Transformer:引领深度学习新时代的架构
  • 深入探究Python中`__init__.py`文件的奥秘
  • SOA半导体光放大器在光纤光栅解调系统中的应用分析
  • python三维矩阵的维度
  • 将输入帧上下文打包到下一个帧的预测模型中用于视频生成
  • 什么是区块?
  • 【Java】Hibernate的检索方式的概述
  • pytest心得体会
  • Linux避免文件误删详解(Linux Avoids File Deletion Errors with Detailed Explanation)
  • 深入剖析TCP协议(内容一):从OSI与TCP/IP网络模型到三次握手、四次挥手、状态管理、性能优化及Linux内核源码实现的全面技术指南
  • Python----深度学习(神经网络的过拟合解决方案)
  • 单调栈-每日温度
  • 1、AI及LLM基础:OpenAI 开发
  • 手写深拷贝函数
  • 基于RabbitMQ实现订单超时自动处理
  • 服务器编译环境配置及数据接收脚本编写(11)
  • 蓝桥杯 19. 最大比例
  • 【3】CICD持续集成-k8s集群中安装Jenkins-agent(主从架构)
  • 【数据可视化-24】巧克力销售数据的多维度可视化分析
  • 解读大型语言模型:从Transformer架构到模型量化技术
  • 3小时速通Python-Python学习总部署、总预览(一)