lovelmh13/myBlog

JavaScript 实现精准定时

Opened this issue · 0 comments

JavaScript 实现精准定时 —— self-adjusting timers 自动调节定时器

JavaScript 由于事件循环的问题,宏任务都可能会被推迟。当遇到一个占用线程很久的代码,使用定时器就会被往后推迟

console.time()
setInterval(() => {
    console.log('我是定时器!')
    console.timeEnd() // default: 9480.052001953125 ms
}, 1000);
for (let i = 0; i<10000000000; i++) {}
console.time()
setTimeout(() => {
    console.log('我是定时器!')
    console.timeEnd() // default: 9659.481201171875 ms
}, 1000);
for (let i = 0; i<10000000000; i++) {}

所以,我们需要来修正这些因为执行时间超长的代码带来的定时器的延后问题。

可以来做一个 自动调节定时器 ( self - adjusting timers) 来消除定时不准的问题,当然还是会有毫秒级的延时,不会完全没有的。

setTimeout 递归实现

这里找了两个版本,看起来都还可以

版本一 超高精度的秒杀倒计时

原理是倒计时前记录当前时间 t1,递归时记录递归次数 c1,每一次递归时,用当前时间 t2 - (t1 + c1 * interval) 得到是误差时间 offset,使用 interval - offset 即可得到下一个 setTimeout 的时间。(我并没有懂是怎么出来的这个公式)

// 阻塞线程
setInterval(function () { 
  var j = 0; 
  while(j++ < 1000000000); 
  console.log('setInterval')
}, 0)

cosnt t1 = Date.now()
let c1 = 0 // 递归此时
let timer = null // 计时器
let t = 5 // 倒计时秒数
let interval = 1000 // 间隔,单位 ms

function countDown () {
  if (--t < 0) {
    clearTimeout(timer)
    return
  }
  
  // 修正误差
  const offset = Date.now() - (t1 + c1 * interval)
  const nextTime = interval - offset
  
  c1++
  console.log(t)
  console.timeEnd()
  console.time()
  timer = setTimeout(countDown, nextTime)
}
countDown()

版本二 How to create an accurate timer in javascript?

// 阻塞线程
setInterval(function () { 
  var j = 0; 
  while(j++ < 1000000000); 
  console.log('setInterval')
}, 0) 

var interval = 1000 // 间隔,单位 ms
var expected = Date.now() + interval
setTimeout(step, interval)

function step () {
  var dt = Date.now() - expected // the drift (positive for overshooting) 
  
  if (dt > interval) {
    // something really bad happened. Maybe the browser (tab) was inactive?
    // possibly special handling to avoid futile "catch up" run
  }
  // do what is to be done
  
  expected += interval
  setTimeput(step, Math,max(0, interval - dt)) // take into account drift
}

秒杀场景下的倒计时

秒杀场景由于客户端的时间是不一致的,所以需要用服务端的时间来统一倒计时。

原理:客户端根据活动开始时间和服务器时间差做倒计时显示.用一个定时器定时,用另一个定时器更新时间变量,进行修正。

const interval = 1000 // 计时间隔
const debounce = 3000 // 修正时间,请求接口间隔
const endTime = Date.now() + 15 * 1000 // 计时次数
let now = Date.now() // 初始时间
let timer1 = null // 倒数计时器
let updateNowTimer = null // 接口请求计时器

// 倒数计时器
timer1 = setInterval(() => {
  now = now + inerval // 根据接口返回的时间进行修正
  const leftT = Math.round((endTime - now) / 1000) // 倒计时次数
  
  console.timeEnd()
  console.time()
  
  if (leftT < 0) { // 倒计时结束
    clearInterval(timer1)
    clearInterval(updateNowTimer)
    return
  }
}, interval)

// 模拟请求接口,更新 now 值
updateNowTimer = setInterval(() => {
  new Promise((resolve) => {
    setTimeout(() => {
      now = Date.now()
      resolve()
    }, 1000)
  })
}, debounce)