嵌套在设置间隔中的设置超时中的计数器在每次回调中多次求和

Counter in set Timeout nested in set interval is summed multiple times per callback

我有一些代码旨在更新设置间隔块内的计数器。我的意图是每次调用设置超时函数时都更新一次计数器。但似乎 setTimeout 的回调在整个超时期间都处于活动状态,因此计数器在 3 秒超时期间重复求和,导致其最终值为 8 而不是所需的值 2。 我试图了解如何在超时后立即调用计数器并且仅调用一次的地方实现这一点。不幸的是,简单地将它放在函数之后似乎并不能解决这个问题。

let count = 1;
let flag = 1;
setInterval(() => {
   if (flag == 1) {
       setTimeout(()=>{
           flag = 0;
           count++;
           }
        ,3000);
    }
},500);

那个?

let count = 1 
  , flag  = 1
  , noTimCall = true
  ;
setInterval(() =>
  {
   if (flag === 1 && noTimCall)
    {
    noTimCall = false
    setTimeout(()=>
      {
      flag = 0
      count++
      noTimCall = true
      }
      ,3000)
    }
  }
  ,500)

让我们逐步检查您的代码。

  • 0 毫秒后开始。 flag1 ⇒ 创建了第一个超时。
  • 开始后 500 毫秒。 flag1 ⇒ …
  • 3000 毫秒自开始。 flag1 ⇒ 第 7 个超时创建。
  • 第一次超时执行:设置flag0,增加count2
  • 自启动以来 3500 毫秒。 flag0 ⇒ 实际上什么都没有。
  • 第二次超时执行:...,将 count 增加到 3
  • 4000 毫秒自开始。 flag0 ⇒ …
  • 第 3 次超时执行:…
  • 6000 毫秒自开始。 …
  • 第 7 次超时执行:...,将 count 增加到 8

如您所见,将创建 7 个超时,每个超时都将 count 增加 1,并在 8 处以 count 结束。

有多种方法可以增加count只延迟一次:

  1. 不使用超时。我们可以手动跟踪已经过去了多少时间,并在达到某个阈值(此处为 3 秒)后增加 count
    同步更改变量意味着它们在间隔回调期间已经有了新值,这与使用回调后更改它们的超时不同。
  2. 增加 count 当且仅当 flag 通过该超时回调从 1 更改为 0。进一步:
    1. 总是创造超时。无论如何,它们内部的检查只会增加 count 一次。
    2. 只创建一个暂停。这将需要一个新变量。
  3. 在间隔回调之外创建超时。但是,这意味着我们无法检查 when 以从我们的间隔内启动它,因为它是在外部立即创建的。

1。跟踪时间

为了跟踪时间,我们需要引入一个新变量,在我们的间隔回调结束时增加间隔的延迟。

现在我们只需要根据阈值(此处:3000 毫秒)检查经过的时间,并在满足该条件后执行特定操作。

我们可以通过允许 if 块 en-/disabled 来进一步扩展我们的代码。这可以使用布尔值简单地完成。

let count = 1;
let flag = 1;

let timePassed = 0; // Tracks passed time
let isStoppingEnabled = true; // Allows toggling of the stopping if-block
// Re-starting the timer requires re-setting both variables above; see last arrow expression below

setInterval(() => {
  if (flag == 1) {
    
    // Runs once 'timePassed' reaches its threshold of 3000
    // and this if-block is "enabled" (see 'isStoppingEnabled')
    if (isStoppingEnabled && timePassed >= 3000) { 
      // Will "disable" _this_ if-block;
      // disabling outer if-block using 'flag = 0;' also works
      isStoppingEnabled = false; 
      count++;
    }
  }
  
  console.log(count);
  
  timePassed += 500; // Remember to increase 'timePassed' by the interval's delay
}, 500);

document.querySelector('#re-stop').addEventListener('click', () => {
  isStoppingEnabled = true; // Re-enable said if-block
});
document.querySelector('#re-stop-track').addEventListener('click', () => {
  isStoppingEnabled = true; // Re-enable said if-block and...
  timePassed = 0; // re-start time tracking
});
body { /* Ignore; better styling */
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 1rem 0.5rem;
}
<button id="re-stop">Re-enable stopping</button>
<label for="re-stop">
  Since 'timePassed' will most likely be already<br>
  over its threshold of 3000, 'count' will be increased almost immediately. 
</label>
<button id="re-stop-track">Re-enable stopping and re-set time-tracker</button>
<label for="re-stop-track">
  Will re-set 'timePassed', effectively re-starting<br>
  the 3 second timer until 'count' is increased.
</label>

注意:小心整数“溢出”! JavaScript 不会对 unsafe integers 的用法发出警告!确保将时间跟踪变量保持在安全的整数范围内。

2。使用 setTimeout()

我们可以创建超时,使用简单的 if 语句检查它们是否应该执行。

2.1 允许多次超时

一个简单的 运行 当 flag 设置为 1 时,它看起来像这样:

let count = 1;
let flag = 1;

setInterval(() => {
  if (flag == 1) {
    setTimeout(() => {
      if (flag == 1) {
        flag = 0;
        count++;
      }
    }, 3000);
  }
  
  console.log(count);
}, 500);

但是,只要创建的第一个超时没有运行,就会创建超时,因为直到那时,if 语句才会执行,进一步创建超时。从技术上讲,这会工作得很好,但实际上,它会造成 不必要的超时 产生 不必要的开销 .

2.2 限制超时创建

我们可以使用单个布尔值限制一次超时的数量,因为我们只想知道一个是否已经创建。

let count = 1;
let flag = 1;

// Prefixed with "_" (underscore) because it has
// no further relevance outside of this script; should actually be private
let _isTimeoutCreated = false;

setInterval(() => {
  if (flag == 1) {
    if (!_isTimeoutCreated) { // Only create a timeout when none was created before
      _isTimeoutCreated = true; // Dis-allow creation of new timeout
      
      setTimeout(() => {
        _isTimeoutCreated = false; // Allow creation of new timeout
        flag = 0;
        count++;
      }, 3000);
    }
  }
  
  console.log(count);
}, 500);

3。在 setInterval()

之外创建超时

最简单的方法是在创建间隔后创建一个超时。
两者几乎同时创建,因为超时和间隔是异步的,允许进一步执行当前调用。

如果不是,那么脚本将停留在该行,使您的网站无响应(因为渲染和脚本执行共享同一个线程)。

它们是异步的(这里的意思是:“非阻塞”)意味着,当间隔执行时,超时计时器仍会计时,在间隔创建 3 秒后执行。

这个解决方案的问题是,如果您想在开始循环后立即开始停止过程,它只是一个可行的解决方案。您可以从您的间隔内开始一个新的超时,但这会使您再次使用第 2.1 点或第 2.2 点。

let count = 1;
let flag = 1;

setInterval(() => {
  if (flag == 1) {
    console.log(count);
  }
}, 500);

setTimeout(() => {
  flag = 0;
  count++;
  console.log(count);
}, 3000);

尾注

显然,还有其他方法可以做到这一点,比如清除间隔并为 starting/stopping 创建一个新间隔。这可以包装在实用程序中-class。或者可以使用现有库的计时器。或者有人可以找到一种我无法想到的方法。

最后一个重要的注意事项是始终考虑您产生的开销,尤其是在性能很重要的环境中。 但是(!) 如果没有必要,不应该仅仅因为性能原因而改变他的方式到不同的实现。