Source Code Analysis of setTimeout() and setInterval()

Recently, the author used the setInterval function in the development process, because we need to render the page dynamically, instead of refreshing the page data all the time, but let the browser render the data dynamically according to the needs of the client. But what is the difference between this function and setTimeout()?We know:

  • setTimeout() is a method bound to the browser window that allows you to specify how many milliseconds (ms) a piece of code or function will execute and return the number of the timer. Of course, we can cancel code execution by clearTimeout()
  • setInterval() is a method bound to the browser window that allows you to specify how many milliseconds (ms) a piece of code or function is timed to execute and return the number of that timer. Of course, we can cancel the execution of code by clearInterval()
    The most effective and direct way to do this is to do a small experiment:
    The setInterval() execution method actually queues the code to be executed until it is the code's turn to execute, determines if the time has elapsed, and executes the code when it arrives
    var startTime=new Date();
    var func = function(){
    console.log('start: ' + (new Date()-startTime));
    for(var i=0; i<1000000000; i++){}
    console.log('end: ' + (new Date()-startTime));
    };
    setInterval(func,1000);

    After executing the above code, you will find that the end and start time of setIntervalis jumping very much, not 1000ms that we set.Since setInterval is a point in time to execute from the start, when the registered function (func) exceeds, it will not be a fixed 1000ms.

    setTimeout() is used roughly the same as setInterval, except that it is executed on a regular basis, and we also test delayed execution

    var startTime=new Date();
    var func = function(){
    console.log('start: ' + (new Date()-startTime));
    for(var i=0; i<1000000000; i++){};
    console.log('end: ' + (new Date()-startTime));
    setTimeout(func,1000);
    };
    setTimeout(func,1000);

    Looking at the figure below, we can see that setTimeout sets the time interval that may be delayed due to the code currently being executed by the task queue. We can see that the time interval between the end and start of the func execution basically matches the 1000ms we have set

    Seeing through the phenomenon, we might as well look at the source of these two functions.

    From the function declaration, you know that setInterval allows arguments here, allowing us to pass in a method that executes after a certain number of times, whose time wait depends on the scheduler_scheduler, and the method execution is regulated by _fnAndFlush, but because of the _requeuePeriodicTimer methodThe time interval is not our fixed 1000ms

....
case 'setInterval':
  task.data!['handleId'] = this._setInterval(
      task.invoke, task.data!['delay']!,
      Array.prototype.slice.call((task.data as any)['args'], 2));
  break; 
....

private _setInterval(fn: Function, interval: number, args: any[]): number {
  let id = Scheduler.nextId;
  let completers = {onSuccess: null as any, onError: this._dequeuePeriodicTimer(id)};
  let cb = this._fnAndFlush(fn, completers);

  // Use the callback created above to requeue on success.
  completers.onSuccess = this._requeuePeriodicTimer(cb, interval, args, id);

  // Queue the callback and dequeue the periodic timer only on error.
  this._scheduler.scheduleFunction(cb, interval, args, true);
  this.pendingPeriodicTimers.push(id);
  return id;
}  

Let's start with the _fnAndFlush code, which actually flushes the function we need to execute into memory and waits for the browser to execute

private _fnAndFlush(fn: Function, completers: {onSuccess?: Function, onError?: Function}):
    Function {
  return (...args: any[]): boolean => {
    fn.apply(global, args);

    if (this._lastError === null) {  // Success
      if (completers.onSuccess != null) {
        completers.onSuccess.apply(global);
      }
      // Flush microtasks only on success.
      this.flushMicrotasks();
    } else {  // Failure
      if (completers.onError != null) {
        completers.onError.apply(global);
      }
    }
    // Return true if there were no errors, false otherwise.
    return this._lastError === null;
  };
}

Let's take a look at the features of the functionality accomplished by this _requeuePeriodicTimer method.It queues functions that exist in the browser's memory (if they exist) for execution, and the function function completes in the next scheduler execution cycle, with a timer waiting for the next function to execute.As in the experiment, the start interval we observed was approximately 1000ms

private _requeuePeriodicTimer(fn: Function, interval: number, args: any[], id: number): Function {
  return () => {
    // Requeue the timer callback if it's not been canceled.
    if (this.pendingPeriodicTimers.indexOf(id) !== -1) {
      this._scheduler.scheduleFunction(fn, interval, args, true, false, id);
    }
  };
}

For setTimeout, pass arguments are allowed here, allowing us to pass in a method that executes after a certain number of times, and its time wait depends on the scheduler, which is what makes our function time interval match the 1000ms we set, and the method execution is regulated by _fnAndFlush Because setTimeout does not push execution functions into the queue, the timer does not run until the previous cycle of functions has been executed before it begins to move on to the next cycle, as written in the experimental code, waiting for 1000ms

...
case 'setTimeout':
  task.data!['handleId'] = this._setTimeout(
      task.invoke, task.data!['delay']!,
      Array.prototype.slice.call((task.data as any)['args'], 2));
  break;
...
private _setTimeout(fn: Function, delay: number, args: any[], isTimer = true): number {
  let removeTimerFn = this._dequeueTimer(Scheduler.nextId);
  // Queue the callback and dequeue the timer on success and error.
  let cb = this._fnAndFlush(fn, {onSuccess: removeTimerFn, onError: removeTimerFn});
  let id = this._scheduler.scheduleFunction(cb, delay, args, false, !isTimer);
  if (isTimer) {
    this.pendingTimers.push(id);
  }
  return id;
}   

Reference material:
https://www.jeffjade.com/2016/01/10/2016-01-10-javacript-setTimeout/
https://www.jeffjade.com/2016/01/10/2016-01-10-javaScript-setInterval/

Keywords: Javascript

Added by s_dhumal on Sun, 01 Sep 2019 20:09:12 +0300