How time is controlled in setTimeout and setInterval in JavaScript

for example, I wrote a 5-second timer or delay timer. How do js and browsers control this time, that is, why it is executed 5 seconds later, how is it internally implemented, why it won"t be executed 5 seconds ago, and how is this time controlled? I especially want to know this question, and I can"t find the corresponding answer on the Internet.


personal understanding, for reference only:
js is a single-threaded mode, which is supposed to execute code blocks sequentially and put them in the task queue for execution, so there is the concept of blocking, that is, the next code segment will not be executed until the previous code segment has been executed. But timers and things like Ajax are executed asynchronously, which seems a bit multithreaded, but it's actually single-threaded in JS, but browsers have multiple threads. So after the timer is set, it will also be put in the task queue, but it will not be executed until after the delay time you set. As for the working principle of the timer said by the landlord, it should be something at the bottom of the floor, so I don't know much about it. I'm sorry. The landlord can refer to the following figure, the execution order in the js engine line.

clipboard.png


it's the timer. All JavaScript engines have a timer timer,. When setTimeout is called, the JS engine will put the passed function into the event queue after the set ms and wait for the main thread to be called.

the knowledge involved here includes threads, Synchronize, async, events, and event loops. As the subject is concerned with how to control the delay time, so these problems will not be repeated, but these knowledge points are worth learning in depth.


simple understanding:
js is single-threaded, and the methods in the timer (and other asynchronous methods, including ajax callback, promise's then) will be put into the event stack. Only after all the code in the thread has been executed will it take the corresponding methods from the event stack to meet the execution conditions and put them in the processing thread, and then go to the event stack after processing and keep looping. So in fact, the delay of 5 seconds is not the exact time, it is only after processing the transaction in the current thread (it may take 6 seconds to finish) and then fetch it from the event stack (to determine whether it has exceeded 5 seconds)


I don't know how the V8 engine is implemented. For details, you have to look at the documentation and source code. I was going to write it in the comments, but I felt that what I was going to say should have some reference value emmm

the only way I can think of is polling + busy waiting. Here are my two guesses

JavaScript's runtime runs a non-stop timing Timer thread, which keeps up with the system time Synchronize. Each time a user calls setTimeout or setInerval , the task and its timing information are put into a list, for example: [timeout task: executed when time is 123ms, interval task: executed when time is 456ms,.] , and then traverse the task list indefinitely (polling). Compare the scheduled execution time of each task with the current system time, and when the time is up, throw the callback function of the task into the execution queue of the main thread, and delete the task or set the next execution time of the task according to the task type (timeout or interval).

The second guess, I think, is more reasonable. The difference from the first is that instead of opening a single thread for Timer, the main thread executes Timer's tick-tick function every event loop trip, traversing all tasks each time and throwing the tasks that are due into the execution queue. It's equivalent to this

.
(function (global) {
  class Timer {
    constructor() {
      this.schedule = []
      this.timeoutHandler = 1
      this.intervalHandler = 1
      this.tick()
    }

    tick() {
      const now = Date.now()
      let nextSchedule = []
      for(let task of this.schedule) {
        if(task.time >= now) {
          // Timeout!
          if(task.type == 'timeout') {
            setImmediate(task.fn)
          } else if(task.type == 'interval') {
            setImmediate(task.fn)
            task.time += task.interval
            nextSchedule.push(task)  // Schedule next execution
          }
        } else nextSchedule.push(task)  // Not yet
      }
      this.schedule = nextSchedule
      setImmediate(() => this.tick())
    }

    setTimeout(fn, time) {
      this.schedule.push({
        fn,
        type: 'timeout',
        time: Date.now() + time,
        handler: this.timeoutHandler
      })
      return this.timeoutHandlerPP
    }

    setInterval(fn, interval) {
      this.schedule.push({
        fn,
        interval,
        type: 'interval',
        time: Date.now() + interval,
        handler: this.intervalHandler
      })
      return this.intervalHandlerPP
    }

    clearTimeout(handler) { ... }
    clearInterval(handler) { ... }
  }

  const timer = new Timer()
  global.setTimeout = function setTimeout(fn, time) { return timer.setTimeout(fn, time) }
  global.setInerval = function setInterval(fn, interval) { return timer.setInterval(fn, interval) }
  ...
})(window)

  • first of all, understand that js is executed in a single thread, and that the browser provides a js engine to execute
  • .
  • but several other threads are provided for asynchronous browsers, and the timer thread is one of them. When the js code executes an asynchronous task like a timer, it is handed over to the timer thread to execute, and the browser still does not hinder the downward execution of Synchronize's code
  • The
  • timer thread operates on the timer task and listens for the time. When the predetermined time is reached, the timer task is placed in a task queue
  • .
  • back to the js main engine, the js code continues to execute Synchronize. When all the tasks of this thread executed by js are finished, go to the task queue to take out the tasks put into it by other threads and execute them in the js execution thread. After the js thread has no tasks, it goes to the task queue to fetch this process is called event loop .
  • so the scheduled time of the timer is only the time that the timer thread listens. When the time is up, the callback function of the timer will not be executed immediately, but will be placed in the task queue until the Synchronize task of the main thread is finished. So this time is not very accurate. Affected by the complex execution time of the code, the scheduled time is only the fastest time that can be executed.
  • so no matter how little the timer time in the js code is, even if it is zero, it will be executed at the end of the program. of course, the minimum default of the W3C standard is 20ms .

the browser engine has a JS engine, which no doubt specializes in parsing JS code. At the same time, browsers also have their own time module. I like this name, and you can also understand it as other aspects, and setTimeout and setInterval both belong to this time module. And the most important thing is that the timer is asynchronous, that is, only after Synchronize's code is completed will the asynchronous code be executed. This is the so-called thread spare time, event loop. So brother, if you have to say that the bottom wants this answer to help you, of course it's my own understanding

Menu