Front-end web countdown is a very common application, we can always see it in the second kill activities of major shopping websites. But in the actual situation, we often find that when the web page is not refreshed and the countdown program continues to run, the display time will be slower and slower than the actual time. I believe that we also have the experience of constantly refreshing the page when the second kill time is coming. And it’s not hard to understand why: Timer (setTimeout or setInterval) is usually used for countdown, and the single-threaded nature of JavaScript makes asynchronous tasks in the task queue unable to be executed in time when there is a block in the execution stack of the master thread. Therefore, the browser cannot guarantee that the code will always execute on time after the timer expires, which causes the countdown to be biased.

The usual solution is for the front end to send a request to the server to get the latest time difference to calibrate the countdown time, active (program set timer request) or passive (F5 has been broken by the user). This method is simple and a little rough, but here is a way to correct the countdown somewhat independently of the server. The code is not original, the source is forgotten for a long time, so record the learning process here so as not to forget. If there is infringement please contact me.

First we need to simulate an environment in which the main thread is blocked without having it block all the time:

setInterval(function(){ 
  let j = 0
  while(j++ < 100000000)},0)
Copy the code

Then the main code:

const interval = 1000
let ms = 50000.// The time difference calculated from the server and the activity start time. Here the test uses 50000 ms
let count = 0
const startTime = new Date().getTime()
let timeCounter
if( ms >= 0) {
  timeCounter = setTimeout(countDownStart, interval)
}
 
function countDownStart () {
   count++
   const offset = new Date().getTime() - (startTime + count * interval) // A
   let nextTime = interval - offset
   if (nextTime < 0) { 
       nextTime = 0 
   }
   ms -= interval
   console.log(` error:${offset}Ms, next execution:${nextTime}After MS, there are also:${ms} ms`)
   if (ms < 0) {
     clearTimeout(timeCounter)
   } else {
     timeCounter = setTimeout(countDownStart, nextTime)
   }
 }
Copy the code

The basic principle of the code is not complicated: the countdown operation is performed by recursively calling setTimeout. Each time the function is executed, A count variable will be maintained to record the countdown times that have been executed. The formula at code A can be used to calculate the deviation between the current countdown time and the actual countdown time, and then the next countdown time can be calculated.

This article was first published on my blog (click here to view it).