scenario

I recently encountered a requirement in a project to display a timer per session. Later found a bug, the time keeps showing 0. After investigation, it is found that the time difference is calculated using the current client time – the server time contained in the message. When the computer time is later than the network time (smaller), the difference value is negative, and 0 will be displayed here.

Now – msgTime, so now needs to be changed to server time.

plan

Step1 obtain the server time

Get server time directly, there will be network latency. The NTP principle is used to obtain the precise server time. Network Time Protocol (NTP) is a Protocol used to synchronize computer Time. Here’s how it works:

The following figure shows a process from request to response:

  • T1: time when the client sends a request
  • T2: indicates the time when the server receives the request
  • T3: the server returns the response time
  • T4: the client accepts the response time
  • D /2: one-way network transmission time

Getting the time from the server should get T3, so there is a T4-T3 (response process) network delay when the client receives this time. Note that it is not T4-T1.

To calculate this difference, you cannot directly calculate T4-T3 because one is client time and the other is server time. Therefore, one-way network transmission time cannot be obtained directly.

T4-t1 can be calculated first, and the result is the time of the client from sending a request to receiving a response. The bidirectional network transmission time can be obtained by removing the server processing time, and then divided by 2 to obtain the difference delay of T4-T3.

Network delay delay: delay = (T4-T1 – (T3-T2)) / 2

ServerTime serverTime: serverTime = T3 + delay

ServerTime: gap = serverTime – new Date().getTime()

The gap can then be used to correct the client time, instead of reobtaining the server time every time, it can be synchronized once in a while.

Step2 timer

A, setInterval

1. Multiple sessions are implemented using the same setInterval timer

The initial idea is that each session defines a timer:

mounted() {
    this.duration = now - lastMsgTime;
    setInterval(() = > {
        this.duration++;
    }, 1000)}Copy the code

This is not necessary, we can pull out all the session data and use the same timer loop session to calculate:

var consults = [
    {
        consultId: 1.lastMsgTime: 1605679800226.duration: 0
    }, {
        consultId: 2.lastMsgTime: 1605679800326.duration: 0}]setInterval(() = > {
    consultTime.forEach((item) = >{ item.duration++; })},1000)
Copy the code

In a callback, the duration is incremented by one, but this causes the following problems.

2. The new session receiving time is in the middle of the timing cycle

When a new session is received, it may be 0.1 seconds before the next timer expires, so just 0.1 seconds will add 1s to the session. So you can’t just increment the duration by one in a callback.

The duration needs to be recalculated using the current server time-message time when the timer callback is executed. The first scheme can basically achieve the required functions.

setInterval(() = > {
    consults.forEach((item) = > {
        // Calibrate according to current client time and gap
        let serverTimeNow = new Date() + gap; item.duration = serverTimeNow - item.lastMsgTime; })},1000)
Copy the code

But we all know that setInterval is actually inaccurate.

3. The setInterval loop is incorrect

Why is it not accurate

  • You can think of a setInterval as a timing interval and a callback interval.

  • The timed part is executed by the browser’s timer trigger thread, unlike the JS main thread which needs to be blocked in the execution queue, so the timing is more accurate.

  • The other part of the callback function, after the timer expires, will queue up the task execution queue, blocked by the previous task, so the execution time is not accurate.

The first solution above can also solve the problem of inaccurate setInterval.

It guarantees that duration is accurate each time a callback is executed; However, the execution interval of the callback cannot be guaranteed, resulting in the inability to stabilize the jump second. The numbers change faster and slower.

A second solution to this problem is to recursively call setTimeout, each time correcting for the delay of the next callback. Is to dynamically set the timer interval. Duration is also evaluated in the callback.

  let count = 0;
  let start = new Date().getTime();
  // To avoid recursion without exit conditions, the actual item can be the page exit empty timer
  let stop = false;
  function countTime() {
    let now = new Date().getTime();
    let delay = now - (start + count * 1000); // Last time I used 1.2s
    count++;
    let intervalGap = 1000 - delay; / / 0.8 s next time
    let timeout = intervalGap > 0 ? intervalGap : 0;
    setTimeout(() = > {
      console.log('Execution was delayedThe ${new Date().getTime() - start - count * 1000}ms`)
      if(! stop) { countTime(); } }, timeout) }setTimeout(() = > {
    stop = true;
  }, 1000 * 60)
  countTime();
  // If the delay is too long, you can see obvious continuous changes
  setTimeout(() = > {
    let i = 0;
    while (i < 1000000000) { i++ };
  }, 0)
  setTimeout(() = > {
    let i = 0;
    while (i < 1000000000) { i++ };
  }, 2000)
Copy the code

Only when the timing is affected by the synchronized code can the next loop be accurately corrected, not affected by the blocking of the previous loop.

4. Optimization point: Align with the system time and seconds to synchronize jump seconds, jump seconds (countdown)

The above scheme can add a bit of optimization by first setting the timer interval with the number of seconds aligned.

let count = 0; let start = new Date().getTime(); // To avoid recursion without exit conditions, the actual item can be the page exit empty timer let stop = false; Let firstTimeout = 1000 - start % 1000; function countTime() { let temp = new Date().getTime(); let delay = temp - (start + count * 1000); count++; let intervalGap = 1000 - delay; let timeout = intervalGap > 0 ? intervalGap : 0; SetTimeout () = > {the console. The log (` execution time stamp ${new Date (). The getTime ()} `) if (! stop) { countTime(); }}, timeout)} setTimeout(() => {start = start + firstTimeout; countTime(); }, firstTimeout) setTimeout(() => { stop = true; }, 1000 * 60) setTimeout(() => { let i = 0; while (i < 1000000000) { i++ }; }, 0) setTimeout(() => { let i = 0; while (i < 1000000000) { i++ }; }, 2000).Copy the code

Except for large deviations due to blocked timestamps, the rest of the executions were within 1ms of the whole second. (Error occurs when the callback is blocked, and the single threaded JS mechanism cannot resolve this problem.)

5. Special case: The browser is running in the background

SetInterval is slow when the TAB page is not activated on the PC or the browser is running in the background.

let count = 0;
let time = new Date().getTime();
setInterval(function(){
    count++;
    let temp = new Date().getTime();
    console.log(count,temp-time)
    time = temp;
},1000)
Copy the code

Experiment with the following code on the console, switch to another TAB and wait for some time. You can see that the time interval varies considerably

The solution is to correct the time when reopening the page. The setInterval above can be implemented, but only when the next callback is executed. The document visibilityChange event listens for TAB display and hide so that the time can be corrected immediately after the page is displayed.

document.addEventListener('visibilitychange'.() = > {
    console.log('change')
    // Time correction logic
});
Copy the code

In addition to setInterval and setTimeout, there are other timer schemes.

Second, the requestAnimationFrame

window.requestAnimationFrame(callback);

  1. RequestAnimationFrame callback execution interval is dependent on browser refresh frequency. If the browser refreshes 60 times a second, the execution interval is 1/60 = 16.7ms; If the browser is downclocking for performance reasons, the interval will change accordingly.

  2. The advantage over setInterval is “checking in”. The callback must be executed before the browser renders, just before the page changes show up. This is something that setInterval cannot do with the same time interval.

  3. But it suffers from the same problem as setInterval: the callback function is still executing in the main thread, blocks, and needs to be corrected in the callback. When the browser runs in the background, it may be stopped.

Third, web worker

Creating a new thread to execute the callback without blocking the main thread’s execution queue is more accurate than setInterval.

After the calculation is complete, the main thread is eventually notified to perform subsequent operations.

conclusion

The above are several timer schemes, you can choose according to the actual needs.

reference

How to accurately obtain the server time – NTP principle CSDN

How the browser works: From the time the URL is entered until the page is loaded