An overview of

We all know that JS is single threaded, and methods such as setTimeout and setInterval do not execute exactly as we expect. You can usually use the functions setTimeout or setInterval to execute a periodic event, and for simple tasks, the goal can be achieved. The real problem is that when you have multiple tasks, interspersing them with time-consuming actions can affect the time of the next task. So there are a few things we can do to make it as timely as possible.

Method 1 uses the Date class

We can use the system clock to compensate for the inaccuracy of the timer. We calculate the error from a series of setTimeout calls and subtract it from the next iteration.

    const PENDING = 'PENDING'
    const STARTED = 'STARTED'
    const FULFILLED = 'FULFILLED'
    class accurTimer {
        constructor(options) {
            this.state = PENDING
            this.count = 0
            this.timer = options.timer || 100
            this.max = options.max || 1
            this.repeatArgument = options.repeatArgument || (() = > {})
            this.onFulfilled = null
        }
        start() {
            this.state = STARTED
            let timeStart = new Date().getTime();
            this.timerId = setTimeout(this.loop.bind(this,timeStart),this.timer)
            return this
        }

        loop(timeStart) {
            if(this.count < this.max) {
                let executime = new Date().getTime()
                let fix = executime - timeStart - this.timer
                setTimeout(this.loop.bind(this,executime),this.timer - fix)
                this.repeatArgument(this.count)
                this.count++
            } else {
                this.state = FULFILLED
                if(this.onFulfilled) {
                    return this.onFulfilled()
                }
            }
        }

        finish(onFulfilled) {
            if(this.state == FULFILLED) {
                return onFulfilled()
            } else {
                this.onFulfilled = onFulfilled
            }
        }

        stop() {
            clearTimeout(this.timerId)
        }
    }
Copy the code

We can use it to simulate type just like the tailwindcss home page does to simulate code input. As a demo

Method 2: Use the Web worker

We can use Web workers to place periodic tasks in the worker to minimize the influence of the main thread.

const worker = ` const timerIdToId = {}; onmessage = function (event) { let data = event.data, name = data.name, timerId = data.TimerId, time; if(data.hasOwnProperty('time')) { time = data.time; } switch (name) { case 'setInterval': timerIdToId[timerId] = setInterval(function () { postMessage({timerId: timerId}); }, time); break; case 'clearInterval': if (timerIdToId.hasOwnProperty (timerId)) { clearInterval(timerIdToId[timerId]); delete timerIdToId[timerId]; } break; case 'setTimeout': timerIdToId[timerId] = setTimeout(function () { postMessage({timerId: timerId}); if (timerIdToId.hasOwnProperty (timerId)) { delete timerIdToId[timerId]; } }, time); break; case 'clearTimeout': if (timerIdToId.hasOwnProperty (timerId)) { clearTimeout(timerIdToId[timerId]); delete timerIdToId[timerId]; } break; }} `

        class WebWorker {
            static maxTimerId = 0x7FFFFFFF;
            constructor(worker) {
                const blob = new Blob([worker])
                this.callbackList = {}
                this.lastTimerId = 0
                this.generateTimerId = this.generateTimerId.bind(this)
                this.setTimeout = this.setTimeout.bind(this)
                this.setInterval = this.setInterval.bind(this)
                this.clearTimeout = this.clearTimeout.bind(this)
                this.clearInterval = this.clearInterval.bind(this)
                this.webWorker = new Worker(URL.createObjectURL(blob))
                return Object.assign(this.webWorker,this)}generateTimerId() {
                do {
                    if (this.lasttimerId == this.maxTimerId) {
                        this.lasttimerId = 0;
                    } else {
                        this.lasttimerId ++; }}while (this.callbackList.hasOwnProperty(this.lasttimerId));
                return this.lasttimerId;
            }

            setTimeout(callback,timer) {
                const timerId = this.generateTimerId()
                this.callbackList[timerId] = {
                    callback: callback,
                    isTimeout: true
                };
                this.webWorker.postMessage({
                    name: 'setTimeout'.TimerId: timerId,
                    time: timer
                })
                return timerId
            }

            clearTimeout(timerId) {
                if (this.callbackList.hasOwnProperty(timerId)) {
                    delete this.callbackList[timerId];
                    this.webWorker.postMessage ({
                        name: 'clearTimeout'.timerId: timerId }); }}setInterval(callback,timer) {
                const timerId = this.generateTimerId()
                this.callbackList[timerId] = {
                    callback: callback
                };
                this.webWorker.postMessage({
                    name: 'setInterval'.TimerId: timerId,
                    time: timer
                })
                return timerId
            }

            clearInterval (timerId) {
                if (this.callbackList.hasOwnProperty(timerId)) {
                    delete this.callbackList[timerId];
                    this.webWorker.postMessage ({
                        name: 'clearInterval'.timerId: timerId }); }}}const webWorker = new WebWorker(worker)
        webWorker.onmessage = function (event) {
            let data = event.data,
                timerId = data.timerId
            if (this.callbackList.hasOwnProperty(timerId)) {
                request = this.callbackList[timerId]
                callback = request.callback
                if (request.hasOwnProperty ('isTimeout') && request.isTimeout) {
                    delete this.callbackList[timerId];
                }
                callback()
            }
        };

        let timerId = webWorker.setInterval(() = > {console.log('setInterval')},1000)

        webWorker.setTimeout(() = > {webWorker.clearInterval(timerId)},3000)
Copy the code