scenario

  • Countdown requirement for resending captcha

Timing methods

  • The method of front end can be used for timing: setTimeout/setInterval/requsetAnimationFrame

The most direct implementation

let interval;
let count = 30;
interval = setInterval(()=>{
    count--;
    console.log(`${count}s`);
    if(count==0){
    	clearInterval(interval)
    }
},1000)
Copy the code
  • Time stamp it and see what it looks like

  • Add sync blocking and look at the timestamp result

  • Because the synchronization code is blocked, the execution time of setInterval’s callback function is delayed on the whole. If the synchronization calculation takes a long time or dom redrawing times are too many, the user needs to wait for the sending time of the verification code to be much longer than 30s.

Optimization -requsestAnimationFrame instead of setInterval

let animationFrame; let count = 30; let paintTimes = 0; requestAnimationFrame(function time(){ paintTimes++; if(paintTimes==60){ paintTimes = 0; count--; If (count < 0) {window. CancelAnimationFrame (animationFrame)} else {the console. The log (` ${count} s - perform timestamp ${new Date (). The getTime ()} `) }} / / must not be in the if - else condition judgment animationFrame = window. RequestAnimationFrame (time)})Copy the code
  • Add sync blocking and look at the timestamp result

  • RAF does not change the fact that code execution is blocked by synchronous code. RAF’s callback function is ultimately executed by the main thread, and the main thread is blocked. RAF has the same effect as setInterval(()=>{},1000/60).
  • RAF’s advantage is that the timing of redraw and reflow closely follows the refresh rate of the browser, which setInterval 16.7ms cannot do.

Optimization-settimeout Simulates setInterval

let timeout; let count = 0; let startTime = new Date().getTime(); const countTime = function(){ count++; let target = startTime+count*1000; let now = new Date().getTime(); let offset = target-now<0? 0:target-now; if(count>30){ clearTimeout(timeout); = > {} else {setTimeout (). The console log (` ${30 - count} s - perform timestamp ${new Date (). The getTime ()} `) countTime (); },offset) } }Copy the code
  • Add sync blocking and look at the timestamp result

  • After 11 executions, the end user’s wait time is roughly in the 30s as the execution time of the synchronous blocking delay is corrected.
  • SetTimeout Compares the user’s current time with the correct time before each callback. If the user’s current time exceeds the theoretical time, the callback function will be executed immediately or skipped.
Optimization – Jump second synchronization with system time
let timeout; let times = 0; let startTime = new Date().getTime(); let firstTime = 1000-startTime%1000; setTimeout(()=>{{ startTime = startTime+firstTime; // there is no count++ countTime(); },firstTime) const countTime = function(){ times++; let target = startTime+times*1000; let now = new Date().getTime(); let offset = target-now<0? 0:target-now; if(count>30){ clearTimeout(timeout); = > {} else {setTimeout (). The console log (` ${30 - count} s - perform timestamp ${new Date (). The getTime ()} `) countTime (); },offset) } }Copy the code
  • Add a long synchronization block look at the timestamp

  • In addition to the blocking correction time, the basic guarantee with the system time synchronization jump second
other
  • Time zone problem: New Date() returns the same number of milliseconds in different time zones, but the displayed time varies from time zone to time zone.
  • Background problems: The timer may pause or slow down while the browser is running in the background. The timer should be corrected in time when switching back, and the visibilityChange event can be monitored on the Web side.
conclusion
  • A simple universal countdown function
function countTimer(finalTime, interval, exactly, callback, timeoutCallBack) { let startTime = new Date().getTime(); let firstTime = interval < 1000 ? interval - startTime % interval : 1000 - startTime % 1000; if (finalTime < startTime) { timeoutCallBack(new Date().getTime()); return } let times = 0; let timeout; if (exactly) { firstTime = 0 } else { finalTime = finalTime + firstTime; } setTimeout(() => { count(); startTime = startTime + firstTime; }, firstTime) function count() { times++; let targetTime = startTime + times * interval; if (targetTime <= finalTime) { let now = new Date().getTime(); let offset = targetTime - now; if (offset < 0) { count() } else { timeout = setTimeout(() => { callback(new Date().getTime()); count(); }, offset) } } else { timeoutCallBack(new Date().getTime()); clearTimeout(timeout) return; }}}Copy the code