【问题标题】:setInterval 'tripping' itself and starting a 2nd interval timersetInterval“跳闸”本身并启动第二个间隔计时器
【发布时间】:2021-09-28 22:00:26
【问题描述】:

我编写了一个选项卡功能,可以在一段时间后在选项卡之间切换,这似乎工作得很好,但是我注意到它有时会中断。

在查看setInterval 并查看错误本身后,它在 20-30 分钟后看起来像 setInterval,会自行跳闸并启动第二个计时器。这会导致同时显示 2 个选项卡。

我在网上读到了一点,基本上它与 Javascript 是单线程的有关,这只是 setInterval 的一个基本缺陷,如果它跳闸,它不会重置计时器,它只会创建第二个。是否有为此行程添加错误捕获或运行重复超时功能的更好方法?

jQuery(document).ready(function() {
    function autoPlayTabs(tabTitle1, tabTitle2, tabTitle3, tabTitle4, tabContent1, tabContent2, tabContent3, tabContent4, tabLength, tabTitleMobile1, tabTitleMobile2, tabTitleMobile3, tabTitleMobile4) {
        var actualTabLength = tabLength * 4;
        var tabContainer = jQuery('.elementor-tabs');
        var allTabs = jQuery('.elementor-tabs .elementor-tabs-wrapper .elementor-tab-title');
        var allContent = jQuery('.elementor-tabs .elementor-tabs-content-wrapper .elementor-tab-content');
        var initialTabTimer = null;
        var tabTimer = null;

        tabTitle1.addClass('active'); tabContent1.addClass('active');

        console.log('Setting initialTabTimer');
        initialTabTimer = setTimeout(function(){
            tabTitle1.removeClass('active'); tabContent1.removeClass('active');
            tabTitle2.addClass('active'); tabContent2.addClass('active');

            console.log("Setting Timeout2");
            setTimeout(function(){
                tabTitle2.removeClass('active'); tabContent2.removeClass('active');
                tabTitle3.addClass('active'); tabContent3.addClass('active');

                console.log("Setting Timeout3");
                setTimeout(function(){
                    tabTitle3.removeClass('active'); tabContent3.removeClass('active');
                    tabTitle4.addClass('active'); tabContent4.addClass('active');
                    console.log("Executing Timeout3 Function");
                }, tabLength);
            }, tabLength);
        }, tabLength);

        console.log("Setting tabTimer");
        tabTimer = setInterval(function(){
            tabTitle4.removeClass('active'); tabContent4.removeClass('active');
            tabTitle1.addClass('active'); tabContent1.addClass('active');

            console.log("Setting TimeoutB");
            setTimeout(function(){
                tabTitle1.removeClass('active'); tabContent1.removeClass('active');
                tabTitle2.addClass('active'); tabContent2.addClass('active');

                console.log("Setting TimeoutC");
                setTimeout(function(){
                    tabTitle2.removeClass('active'); tabContent2.removeClass('active');
                    tabTitle3.addClass('active'); tabContent3.addClass('active');

                    console.log("Setting TimeoutD");
                    setTimeout(function(){
                        tabTitle3.removeClass('active'); tabContent3.removeClass('active');
                        tabTitle4.addClass('active'); tabContent4.addClass('active');
                        console.log("Executing TimeoutD Function");
                    }, tabLength);
                }, tabLength);
            }, tabLength);
        }, actualTabLength);

        allTabs.click(function() {
            if (initialTabTimer !== null) {
               clearTimeout(initialTabTimer);
               initialTabTimer = null;
               console.log("Cleared initialTabTimer");
            }

            if (tabTimer !== null) {
               clearInterval(tabTimer);
               tabTimer = null;
               console.log("Cleared tabTimer");
            } else {
                console.log("Did not need to clear tabTimer");
            }
            
            allTabs.removeClass('active');
            allContent.removeClass('active');
            tabContainer.addClass('tabsManual');
        });
    }

    if(homeTabTitle1.length > 0){
    console.log("Calling AutoPlayTabs Homepage");
    autoPlayTabs(homeTabTitle1,homeTabTitle2,homeTabTitle3,homeTabTitle4,homeTabContent1,homeTabContent2,homeTabContent3,homeTabContent4,homeTabLength);
    }
});

下面是一个 console.log,其中包含从它工作到中断到修复自身调用的每个 setTimeout:

Calling AutoPlayTabs Homepage
Setting initialTabTimer
Setting tabTimer
Setting Timeout2
Setting Timeout3
Executing Timeout3 Function

//This console.log block ran 30 times without error//
Setting TimeoutB
Setting TimeoutC
Setting TimeoutD
Executing TimeoutD Function
//This console.log block ran 30 times without error//

//It Breaks here after a total of 24 minutes//
Setting TimeoutB
Setting TimeoutC
Setting TimeoutD
Setting TimeoutB //For some reason TimeoutB has been fired again
Executing TimeoutD Function

Setting TimeoutC
Setting TimeoutD
Setting TimeoutB
Executing TimeoutD Function

Setting TimeoutC
Setting TimeoutD
Executing TimeoutD Function

//It fixed itself here after 2.4 minutes//

Setting TimeoutB
Setting TimeoutC
Setting TimeoutD
Executing TimeoutD Function

【问题讨论】:

  • 您能否提供该问题的工作示例,包括所有相关的 HTML 和 CSS。我问,因为 JS 计时器是出了名的不准确,尤其是在长时间运行时。您的问题很可能可以使用 CSS 动画以更简单、更高效的方式避免和解决,或者至少不需要 7 个嵌套计时器。
  • setInterval() 不会“绊倒”。
  • autoPlayTabs() 是否有任何更改不止一次触发?如果您可以重现错误,请在该函数中放置一个 console.log,以便您可以观察该函数是否被再次调用...
  • 定时器是不准确的,因为当你要求它们(必要的)时它们不会精确地触发。然而,一个自发启动另一个自身副本的setInterval() 设置将是一个严重的浏览器错误。
  • 看起来它是由setIntervalsetTimeout 混合引起的 - 如果您考虑实际超时的 1% 差异,那么您可以看到它不会那么长,直到 @987654331 @ 将更频繁地运行 4x setTimeout(t) - 将您的外部 setInterval 更改为 setTimeout 并在第 4 次之后重新启动它。或者,重构,因此您只需要一次超时,问题就会消失(同时使您的代码更易于维护)

标签: javascript jquery setinterval


【解决方案1】:

如果你只是简化你的逻辑,只运行一个间隔,你所有的同步问题都会消失

 const tabs = [
  {title: tabTitle1, content: tabContent1},
  {title: tabTitle2, content: tabContent2},
  {title: tabTitle3, content: tabContent3},
  {title: tabTitle4, content: tabContent4}
 ];
 
 let curr = 0;
 let tab = tabs[curr];
 tab.title.addClass("active");
 tab.content.addClass("active");

 var timerInterval = setInterval(function(){     
        tab.title.removeClass("active");
        tab.content.removeClass("active");
        curr = ++curr % tabs.length;
        tab = tabs[curr];
        tab.title.addClass("active");
        tab.content.addClass("active");
 }, tabLength);

活生生的例子

jQuery(document).ready(function() {
  function autoPlayTabs(tabTitle1, tabTitle2, tabTitle3, tabTitle4, tabContent1, tabContent2, tabContent3, tabContent4, tabLength, tabTitleMobile1, tabTitleMobile2, tabTitleMobile3, tabTitleMobile4) {
    var actualTabLength = tabLength * 4;
    var tabContainer = jQuery('.elementor-tabs');
    var allTabs = jQuery('.elementor-tabs .elementor-tabs-wrapper .elementor-tab-title');
    var allContent = jQuery('.elementor-tabs .elementor-tabs-content-wrapper .elementor-tab-content');
    var initialTabTimer = null;
    var tabTimer = null;
    
    const tabs = [
        {title: tabTitle1, content: tabContent1},
      {title: tabTitle2, content: tabContent2},
      {title: tabTitle3, content: tabContent3},
      {title: tabTitle4, content: tabContent4}
     ];
     
     let curr = 0;
     let tab = tabs[curr]
     tab.title.addClass("active")
     tab.content.addClass("active");
     var timerInterval = setInterval(function(){
     
            tab.title.removeClass("active")
            tab.content.removeClass("active");
        curr = ++curr % tabs.length
            tab = tabs[curr]
        tab.title.addClass("active")
            tab.content.addClass("active");
     }, tabLength)
    

    allTabs.click(function() {
      if (initialTabTimer !== null) {
        clearTimeout(initialTabTimer);
        initialTabTimer = null;
      }

      if (tabTimer !== null) {
        clearInterval(tabTimer);
        tabTimer = null;
      }

      allTabs.removeClass('active');
      allContent.removeClass('active');
      tabContainer.addClass('tabsManual');
    });
  }

  if ($("#homeTabTitle1")) {
    autoPlayTabs($("#homeTabTitle1"), $("#homeTabTitle2"), $("#homeTabTitle3"), $("#homeTabTitle4"), $("#homeTabContent1"), $("#homeTabContent2"), $("#homeTabContent3"), $("#homeTabContent4"), 1000);
  }
});
.active { background-color:red }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="homeTabTitle1">
  homeTabTitle1
</div>
<div id="homeTabTitle2">
  homeTabTitle2
</div>
<div id="homeTabTitle3">
  homeTabTitle3
</div>
<div id="homeTabTitle4">
  homeTabTitle4
</div>
<div id="homeTabContent1">
  homeTabContent1
</div>
<div id="homeTabContent2">
  homeTabContent2
</div>
<div id="homeTabContent3">
  homeTabContent3
</div>
<div id="homeTabContent4">
  homeTabContent4
</div>

注意我没有连接你的“停止”逻辑,但你需要做的就是在你的click处理程序中clearInterval(timerInterval)

【讨论】:

  • 现在这看起来像是我可以得到的答案,让我实施和测试。
  • 将在 24 分钟后检查是否损坏,祈祷我会研究您的方法,以便在等待时真正理解它。
  • @DanielVickers 我向你保证,24 分钟没有什么特别之处:D 只要页面打开,这将有效。由于超时不同步,您得到了重复的“活动”标签
  • 由于只有一个定时器,它不能“绊倒自己(或其他定时器)”,所以你可以永远离开它
  • @Jamiec 我已将我的点击代码更新为您的计时器,但它不会停止它一直在播放的计时器,您知道为什么吗?
【解决方案2】:

根据要求

您能否使用 setInterval 弹出一个更新的答案

您可以重构为单个 setInterval()。以下是一个概念 - 如果您的选项卡在 HTML 中 not 彼此相邻,那么您可以预先整理它们并循环遍历数组。您还可以使用data- 属性将您的内容链接到您的标签。

这还允许您更改“活动”选项卡(例如通过单击它),并且每次都会“自动播放”到下一个选项卡 - 但在这种情况下您可能需要重置计时器。

var interval_time = 250;  // short time for demo

var tabs = $(".tab");

// single setInterval
var timer = setInterval(() => {

  // get the currently active, so no need to store what that was
  var active = tabs.filter(".active");
  active.removeClass("active");

  // get the next tab, if none, then loop back to the first
  var next = active.next(".tab");
  if (next.length == 0)
    next = tabs.first();
  next.addClass("active");

}, interval_time);

$("#stop").click(() => clearInterval(timer))
/* can show / hide
.tab { display:none; }
.tab.active { display:block; }
*/

/* or show all the tabs at once */
.tab { display:inline-block; color: #CCC; }
.tab.active { color: green }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class='tab active'>tab 1</div>
<div class='tab'>tab 2</div>
<div class='tab'>tab 3</div>
<div class='tab'>tab 4</div>
<br/>
<button type='button' id='stop'>stop</button>

【讨论】:

    【解决方案3】:

    这不是 setTimeout 跳闸,而是 setTimeout 被调用了两次

    为了帮助记录这一点,您可以输入console.logs 以显示每个计时器的启动时间。为简单起见,我也摆脱了所有的类添加和删除。

    我也很好奇homeTabTitle1 的来源——你能澄清一下吗?

    jQuery(document).ready(function() {
      function autoPlayTabs(tabTitle1, tabTitle2, tabTitle3, tabTitle4, tabContent1, tabContent2, tabContent3, tabContent4, tabLength, tabTitleMobile1, tabTitleMobile2, tabTitleMobile3, tabTitleMobile4) {
        var actualTabLength = tabLength * 4;
        var tabContainer = jQuery('.elementor-tabs');
        var allTabs = jQuery('.elementor-tabs .elementor-tabs-wrapper .elementor-tab-title');
        var allContent = jQuery('.elementor-tabs .elementor-tabs-content-wrapper .elementor-tab-content');
        var initialTabTimer = null;
        var tabTimer = null;
    
        console.log("Setting initialTabTimer")
        initialTabTimer = setTimeout(function() {
    
          console.log("Setting timer 2")
          setTimeout(function() {
    
            console.log("Setting timer 3")
            setTimeout(function() {
    
              console.log("Executing inner function 3")
            }, tabLength);
          }, tabLength);
        }, tabLength);
    
        console.log("Setting tabTimer")
        tabTimer = setInterval(function() {
    
          console.log("Setting timber B")
          setTimeout(function() {
    
            console.log("Setting timber C")
            setTimeout(function() {
    
              console.log("Setting timber D")
              setTimeout(function() {
    
                console.log("Executing inner function D")
              }, tabLength);
            }, tabLength);
          }, tabLength);
        }, actualTabLength);
    
        allTabs.click(function() {
          if (initialTabTimer !== null) {
            clearTimeout(initialTabTimer);
            initialTabTimer = null;
            console.log("Cleared initialTabTimer");
          } else {
            console.log("Did not need to clear initialTabTimer");
          }
    
          if (tabTimer !== null) {
            clearInterval(tabTimer);
            tabTimer = null;
            console.log("Cleared tabTimer");
          } else {
            console.log("Did not need to clear tabTimer");
          }
    
        });
      }
    
      if (homeTabTitle1) {
        console.log("calling AutoPlayTabs")
        autoPlayTabs(homeTabTitle1, homeTabTitle2, homeTabTitle3, homeTabTitle4, homeTabContent1, homeTabContent2, homeTabContent3, homeTabContent4, homeTabLength);
      }
    });
    

    为什么 document ready 会触发不止一次?

    正如这里指出的:

    jQuery $(document).ready () fires twice

    最常见的原因是 DOM 中的某些操作导致文档重新呈现。回答者提出了一个解决方案。

    为什么有些人称 setTimeout “不可靠”?

    我认为他们的意思是你不能依赖 when 回调函数会被调用。我认为他们不合理地期望 setTimeout 能够神奇地使 Javascript 在所需时间准确运行代码。实际上,setTimer 只是将任务放在一个任务列表中,当主程序没有事情要做时运行。

    通常,在网页上,在最初呈现页面后,Javascript 解释器几乎无需执行任何操作。在这种情况下,setTimeout 回调将在相当准确的时间被调用。

    但是,如果网页有很多事情需要 Javascript 解释器去做,比如大量计算需要几秒钟甚至几分钟的“思考时间”,那么从表面的评估来看,setTimeout 似乎是不可靠的。但是它并不是真的不可靠,它正在做它应该做的事情,即延迟回调,直到有空闲时间,并且足够的时间过去了。

    如果 Javascript 代码调用异步函数,那么这可能会释放解释器来查看 setTimeout 事件。因此,如果网页准备就绪(从 jQuery 的角度来看),但随后与其他站点进行了几秒钟的通信以获取数据库条目等,那么只要这些数据库调用是异步的,setTimeout 就能够在需要时触发其回调.

    setInterval 和 setTimeout 不同

    “由于某种原因 timeoutB 再次被触发”

    原因是它是setInterval,而不是setTimeout。那是故意的吗? setInterval 会不断地开火。

    【讨论】:

    • 但是为什么在页面加载时只调用一次它会被调用两次呢?那是唯一的一次吗?我已经添加了控制台登录并将一直监控直到它再次中断,但它只在文档准备好时被调用一次。 homeTabTitle1 是检查以确保选项卡在页面上,然后运行该功能。
    • 您认为转到另一个选项卡,然后在半小时左右后返回该选项卡也会刷新文档就绪状态吗?
    • 不,我不这么认为。您可以对其进行测试,但我的经验是,仅更改选项卡并返回并不是一个新的“就绪”状态。我认为时间的流逝也无关紧要。它一定是由代码引起的 DOM 正在积极发生的事情。
    • 用控制台日志更新了我的帖子,它运行了 24 分钟然后中断了 2.4 分钟,然后自行修复了大声笑
    • 是的,这是故意的,setInterval 设置为每 24 秒再次触发一次,以便循环超时并且工作正常,直到它达到 24 分钟,然后在 2.4 分钟后中断并自行修复认为有人对 0.1 秒不准确的评论是这里的答案,因为它正好是 24 分钟和 2.4 分钟
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多