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