The business scenario

Online status Management

Business description

Front-end: the heartbeat interface of the background is periodically called when the staff is online or busy, and the interval of 10 seconds is set in the background. This interface is defined as the heartbeat of whether the staff is online or not. This interface returns back billing, usage permissions, and whether the current online identity. Why not socket? _(:з “Angle)_ could not be implemented.

Background: Scans the heartbeat table every minute to obtain the online status of the current staff and perform corresponding services such as online allocation and access.

Code implementation

SetTimeout, setInterval, requestAnimationFrame

SetTimeout: Queue only once to queue up messages.

setTimeout(()=>{
    console.log(1)
}, 500)
setTimeout(()=>{
    console.log(2)
}, 1000)
setTimeout(()=>{
    console.log(3)
}, 1500)
alert(4)
Copy the code

SetInterval: It needs to be set at each point in time according to the preset interval. SetInterval will not queue if it finds that it is still in the queue. In other words, there’s only one inerval in the queue.

Of course, if you do this three times setInterval will do it three times, but then you have three timers. SetInterval () = > {the console. The log (' execution ')}, 500) alert (1)Copy the code

RequestAnimationFrame: setTimeout, setInterval can be simulated

The final code implementation is done through setTimeout again

Let time heartbeatTimer() {time = setTimeout(() => {clearTimeout(time) // Enable the next heartbeatTimer() // Heartbeat interface request heartbeatTimerApi() }, 10000) }Copy the code

Problems arising

After passing the setTimeout test, the heartbeat interface request at an interval of 10 seconds was realized. However, the tester found that the online status would be offline by the online value returned by the background when he did not change the status.

To solve the problem

At first, from the perspective of Event Loop, I thought that the main application might be performing performance-intensive tasks or have been operating on the page. As a result, synchronous codes have been pushed into the call stack in the Event Loop, so tasks in the message queue cannot be put into the call stack for execution.

Later, it was found that the page was also taken offline by the back end without any operation. The reason was that the background thought that the current staff was offline because the heartbeat remained for 59 seconds and there was no request.

Communication with the test described a scenario in which the browser was sometimes closed to minimize or switched to a different TAB scenario when the worker was online.

Cooperate with browser task manager + described scenario = conclusion

Set a timer when the page is the background (TAB and minimum browser, not activated), as the browser itself thread scheduling strategy is reduced, cause the page timer implementation has also been reduced, cause heartbeat interface directly into will perform a nearly minute, the background just 50 seconds interval of scanning time, _(:з “Angle)_ Other lucky.

Let time = date.now () setInterval(() => {console.log(' time difference from last execution ', date.now () - time) time = date.now ()}, 500)Copy the code

let time = Date.now() function test() { timer = setTimeout(() => { test() const tempTime = Date.now() Console. log(' time difference since last execution :', temptime-time) time = tempTime}, 500)} test()Copy the code

Hey, what if we emulate setTimeout and setInterval with requestAnimationFrame? _ _ (: з < “)

class RAF { constructor () { this.init() } init () { this._timerIdMap = { timeout: {}, interval: {} } } run (type = 'interval', cb, Interval = 16.7) {const now = date.now let stime = now() let etime = stime Const timerSymbol = Symbol() const loop = () => {this.setidMap (timerSymbol, type, loop) etime = now() if (etime - stime >= interval) { if (type === 'interval') { stime = now() etime = stime } cb() type === 'timeout' && this.clearTimeout(timerSymbol) } } this.setIdMap(timerSymbol, type, Loop) return timerSymbol // Return Symbol Ensures that the value returned by setTimeout/setInterval is unique} setIdMap (timerSymbol, type, loop) { const id = requestAnimationFrame(loop) this._timerIdMap[type][timerSymbol]= id } setTimeout (cb, Return this.run('timeout', cb, interval) } clearTimeout (timer) { cancelAnimationFrame(this._timerIdMap.timeout[timer]) } setInterval (cb, Return this.run('interval', cb, interval) } clearInterval (timer) { cancelAnimationFrame(this._timerIdMap.interval[timer]) } } var raf = new RAF() var timer1 = raf.setInterval(() =>{ console.log(1000) }, 1000) var timer2 = raf.setInterval(() =>{ console.log(1500) }, 1500) raf.setTimeout(() => {raf.clearInterval(timer1) raf.clearInterval(timer2)}, 6000) https://juejin.cn/post/6999444668089892901#heading-11Copy the code

Then something even more outrageous, through the simulated timer, will not execute 233 directly when the page cutting background operation, etc.,

Why is that? Since requestAnimationFrame does not get the frequency of browser updates when switching to another page, the callback is not executed.

The solution

Create a new thread through the Web Worker to execute the timer task. At this time, select setTimeout and setInterval will be ok

However, webworkers must be cognizant of their creators

I tried to read the worker.js file statically when the worker.js file is placed in the public folder of the project, but in the online environment, the files under the public file are uploaded to the OSS server, so there is a problem of cross-domain and non-homogeneity. Put the file in the directory and webpack compiles the file name.

To solve this problem, the Blob is used to generate a temporary JS file that can be accessed to avoid the above problem.

function createWorker (f) { var blob = new Blob(['(' + f.toString() + ')()']) var url = window.URL.createObjectURL(blob)  var worker = new Worker(url) return worker } createWorker(()=>{ let time = Date.now() setInterval(() => { const TempTime = date.now () console.log(' tempTime :', temptime-time) time = tempTime}, 500)})Copy the code

Therefore, it is recommended to use worker in the future when the front-end needs rotation training to scheduled tasks, so as not to worry about queue execution of Event Loop and browser thread scheduling.

Tip _(:з “Angle)_

How to tell if the current page has child threads on the console

If there is a request from the initiated child thread, the request also carries a thread icon in front of it under the Network panel