Sometimes we don’t want to execute a function immediately, but rather wait a certain amount of time before executing it. This is called “scheduling a call.”

There are two ways to achieve this:

  • setTimeoutAllows us to defer the execution of a function after an interval of time.
  • setIntervalAllows us to run a function repeatedly, starting after an interval, and then continuously repeating the function at that interval.

These two methods are not in the JavaScript specification. But most runtime environments have built-in schedulers and provide these methods. Currently, all browsers and Node.js support both methods.

setTimeout

Grammar:

let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...)
Copy the code

Parameter Description:

Func | code: want to perform a function or code string. It’s usually a function that’s passed in. Passing in code strings is supported for some historical reasons, but not recommended.

Delay: indicates the delay before execution, expressed in milliseconds (1000 ms = 1 second). The default value is 0.

Arg1, arg2… : A list of arguments to pass to the function (or code string) to be executed (not supported below IE9)

For example, in the following example, the sayHi() method executes after 1 second:

function sayHi() {
  alert('Hello'); } *! *setTimeout(sayHi, 1000); * /! *Copy the code

Case with parameters:

function sayHi(phrase, who) {
  alert( phrase + ', '+ who ); } *! *setTimeout(sayHi, 1000."Hello"."John"); // Hello, John* /! *Copy the code

If the first argument bit is passed in as a string, JavaScript automatically creates a function for it.

So it’s ok to write this:

setTimeout("alert('Hello')".1000);
Copy the code

However, instead of using strings, we can use arrow functions instead, as shown below:

setTimeout(() = > alert('Hello'), 1000);
Copy the code
New developers sometimes mistakenly add a pair of parentheses' () 'to functions:' js' // wrong! setTimeout(sayHi(), 1000); This does not work, because setTimeout expects a reference to the function. 'sayHi()' is clearly executing a function, so setTimeout is actually the result of the execution of the function. In this example, the execution result of 'sayHi()' is' undefined '(that is, the function does not return any results), so nothing is actually scheduled.Copy the code

Use clearTimeout to cancel scheduling

When called, setTimeout returns a “Timer Identifier,” in our case the timerId, which we can use to cancel execution.

Syntax for unscheduling:

let timerId = setTimeout(...). ;clearTimeout(timerId);
Copy the code

In the code below, we schedule a function and then cancel the schedule (with regret). So nothing happened in the end:

let timerId = setTimeout(() = > alert("never happens"), 1000);
alert(timerId); // Timer identifier

clearTimeout(timerId);
alert(timerId); // The same identifier (not null because the schedule was canceled)
Copy the code

From the output of the alert, in the browser, the timer identifier is a number. In other contexts, it could be something else. For example, Node.js returns a timer object that contains a list of methods.

Again, there is no uniform specification for these methods, so this is fine.

For browser environments, timers are described in detail in the HTML5 standard, as described in the Timers section.

setInterval

The setInterval method has the same syntax as setTimeout:

let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...)
Copy the code

All parameters have the same meaning. Unlike setTimeout, which is executed only once, setInterval is executed periodically at a given interval.

To block subsequent calls, we need to call clearInterval(timerId).

The following example will output a message every 2 seconds. After 5 seconds, the output stops:

// Repeat every 2 seconds
let timerId = setInterval(() = > alert('tick'), 2000);

// Stop after 5 seconds
setTimeout(() = > { clearInterval(timerId); alert('stop'); }, 5000);
Copy the code
In most browsers, including Chrome and Firefox, the internal timer continues to tick when the 'Alert/Confirm/Prompt' popup is displayed. So, if you don't close the alert popover for a certain amount of time when you run the code above, the next alert will appear immediately after you close the popover. The interval between the two 'alerts' will be less than 2 seconds.Copy the code

Nested setTimeout

Periodic scheduling can be done in two ways.

One is to use setInterval, and the other is to use nested setTimeout, like this:

/** instead of: let timerId = setInterval(() => alert('tick'), 2000); * /

let timerId = setTimeout(function tick() {
  alert('tick'); *! * timerId =setTimeout(tick, 2000); / / (*)* /! *},2000);
Copy the code

The setTimeout above schedules the next call as soon as the current function completes (*).

Nested setTimeout is much more flexible than setInterval. In this way, the next call can be scheduled based on the results of the current execution, so that the next call can be different from the current one.

For example, we want to implement a server that sends a data request to the server every 5 seconds, but if the server is overloaded, we need to reduce the frequency of requests, such as increasing the interval to 10, 20, 40 seconds, and so on.

Here is the pseudocode:

let delay = 5000;

let timerId = setTimeout(function request() {... Send a request...if (request failed due to server overload) {
    // The next execution interval is twice as long as the current one
    delay *= 2;
  }

  timerId = setTimeout(request, delay);

}, delay);
Copy the code

Also, if we schedule a function that consumes a lot of CPU, we can measure how long it takes to execute and schedule whether the next call should be made earlier or later.

nestedsetTimeoutCan accurately set the delay between two executions, whilesetIntervalCan’t.

Let’s compare the two code snippets. The first uses setInterval:

let i = 1;
setInterval(function() {
  func(i++);
}, 100);
Copy the code

The second uses a nested setTimeout:

let i = 1;
setTimeout(function run() {
  func(i++);
  setTimeout(run, 100);
}, 100);
Copy the code

For setInterval, the internal scheduler executes func(i++) every 100 milliseconds:

Notice that?

usesetIntervalWhen,funcThe actual interval between calls should be shorter than the interval specified in the code!

This is also normal because the time it takes to execute func “eats up” some of the interval time.

It may also be the case that the funC execution takes longer than we expected and is 100 milliseconds longer than we expected.

In this case, the JavaScript engine waits for the func execution to complete, then checks the scheduler, and immediately executes it again if the time is up.

In extreme cases, if the function executes for longer than delay sets each time, there will be no pause at all between calls.

Here is a schematic of nested setTimeout:

nestedsetTimeoutThis ensures a fixed delay (100 milliseconds in this case).

This is because the next call is scheduled after the previous call has completed.

When a function is passed 'setInterval/setTimeout', an internal reference is created for it and stored in the scheduler. This prevents the garbage collector (GC) from collecting the function even if it has no other references. SetTimeout (function() {// This function will remain in memory until the scheduler calls it. }, 100); For setInterval, the passed function also remains in memory until clearInterval is called. There is another side effect to mention. If a function refers to an external variable, then as long as the function exists, so does the external variable. They can take up more memory than the functions themselves. Therefore, when we no longer need the scheduling function, it is best to cancel it, even if it is a small function.Copy the code

Zero delay setTimeout

There’s a special use here: setTimeout(func, 0), or just setTimeout(func).

This scheduling allows funC to execute as quickly as possible. But the scheduler will call it only after the currently executing script has finished executing.

That is, the function is scheduled to execute “as soon as” the current script completes execution.

For example, the following code prints “Hello” and then “World” immediately:

setTimeout(() = > alert("World"));

alert("Hello");
Copy the code

The first line of code “schedules the call to calendar 0 ms.” But the scheduler doesn’t “check the calendar” until the current script has finished executing, so it prints “Hello” first, then “World”.

In addition, there are advanced browser-specific use cases for 0 time-out delays, which we’ll cover in detail in the Info: Event-loop chapter.

In the browser environment, the frequency of nested timers is limited. According to the standard [HTML] (https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) said: After 5 renested timers, the interval is forced to be at least 4 milliseconds. Let's use the following example to see what this really means. Where 'setTimeout' calls reschedule their own calls with zero delay. Each call records the actual time of the previous call in the 'times' array. So what does the real delay look like? Js run let start = date.now (); let times = []; setTimeout(function run() { times.push(Date.now() - start); // Save the delay of the previous call if (start + 100 < date.now ()) alert(times); Else setTimeout(run); }); // Output examples: / / 1,1,1,1,9,15,20,24,30,35,40,45,50,55,59,64,70,75,80,85,90,95,100 ` ` ` for the first time, the timer is the immediate execution of a () as described in the specification, Then we can see '9, 15, 20, 24... `. There must be a mandatory delay of more than 4 milliseconds between calls. The timer array contains the difference between the start time and the start time, so the number only gets bigger and bigger. In fact, the delay is the difference between the array values. A similar thing happens if we use 'setInterval' instead of 'setTimeout' : 'setInterval(f)' runs a few 'f' times with zero delay, and then a forced delay of more than 4 milliseconds. This restriction is from "ancient times" and many scripts rely on it, so the mechanism is still there today. For server-side JavaScript, there is no such limitation, and there are other ways to schedule real-time asynchronous tasks. For example, the Node. Js [setImmediate] (https://nodejs.org/api/timers.html#timers_setimmediate_callback_args). Therefore, this reminder is specific to the browser environment only.Copy the code

conclusion

  • setTimeout(func, delay, ... args)setInterval(func, delay, ... args)The method allows us to be indelayRun after millisecondsfuncOr at a timedelayMilliseconds are intervals that run periodicallyfunc.
  • To cancel the execution of a function, we should callclearInterval/clearTimeoutAnd willsetInterval/setTimeoutThe returned value is passed in as an input parameter.
  • nestedsetTimeoutsetIntervalIt is more flexible and allows us to set the time between executions more precisely.
  • Zero delay schedulingsetTimeout(func, 0)(withsetTimeout(func)Same) to schedule calls that need to be executed as soon as possible, but will be called after the current script has finished executing.
  • The browser willsetTimeoutsetIntervalThe minimum latency for nested calls of five or more levels (after five calls) is limited to 4ms. This is a problem left over from history.

Note that none of the scheduling methods can guarantee an exact delay.

For example, timers in the browser can be slow for a number of reasons:

  • The CPU overload.
  • The browser TAB is in background mode.
  • Laptops run on battery power.

All of these factors can increase the timer’s minimum timer resolution (minimum latency) to 300ms or even 1000ms, depending on the browser and its Settings.

Some chestnuts, please

Split tasks with high CPU usage

Here is a technique for using setTimeout to split cpu-heavy tasks.

For example, a syntax highlighting script (used to color sample code) can be very CPU intensive. To highlight the code, it first analyzes the code, then creates a bunch of colored elements and adds them to the page document — which can take a long time when the text is large. Sometimes it even causes the browser to “hang,” which is clearly unacceptable.

So, we might as well divide the long text into several parts. First process the first 100 lines, then use setTimeout(… ,0) schedule the processing of the next 100 lines, and so on.

To make things easier, consider a slightly simpler example. Let’s say we have a function that counts from 1 to 1000000000.

At runtime, CPU hangs are observed, especially server-side JS. If you run it in a browser, try clicking another button on the page, and you’ll notice that the entire JavaScript execution stops, and nothing can be done until the code is finished running.

let i = 0;

let start = Date.now();

function count() {

  // Perform a time-consuming task
  for (let j = 0; j < 1e9; j++) {
    i++;
  }

  alert("Done in " + (Date.now() - start) + 'ms');
}

count();
Copy the code

On a good day, the browser will also display a warning such as “The script takes too long” (which is unlikely, given that the number given is not particularly large).

Here we use setTimeout to split tasks:

let i = 0;

let start = Date.now();

function count() {

  // Complete some tasks first (*)
  do {
    i++;
  } while (i % 1e6! =0);

  if (i == 1e9) {
    alert("Done in " + (Date.now() - start) + 'ms');
  } else {
    setTimeout(count, 0); // Schedule the next task (**)
  }

}

count();
Copy the code

The browser’S UI interface now works even when “counting” is in progress.

The code at (*) does this step by step:

  1. For the first time:i=1... 1000000The count.
  2. Do it a second time:i=1000001.. 2000000The count.
  3. . And so on, among themwhileStatements to checkiIs it just enough to be1000000Aliquot.

If the task has not been completed, schedule the next call at code (*).

The gap between count calls is long enough for the JavaScript engine to “take a breather” and respond to user actions.

Splitting with setTimeout and not using it split evenly in terms of speed, and the total time taken for the counting process is almost the same.

To further illustrate, the following improvements are made.

Move the scheduling code to the beginning of the count() function:

let i = 0;

let start = Date.now();

function count() {

  // Now put scheduling at the beginning
  if (i < 1e9 - 1e6) {
    setTimeout(count, 0); // Schedule the next call
  }

  do {
    i++;
  } while (i % 1e6! =0);

  if (i == 1e9) {
    alert("Done in " + (Date.now() - start) + 'ms');
  }

}

count();
Copy the code

Knowing that count() will not be executed only once, this time schedule the next count before the count begins.

If you run it yourself, you’ll notice that it takes much less time this time.

Give the browser a chance to render

Interline scripting also has the benefit of showing the user progress bars, etc. The browser does not begin the repainting process until all scripts have been executed.

So, if you run a very time-consuming function, even if you change the content of the document in that function, the change won’t be immediately reflected on the page until the function is complete.

Here’s an example:

<div id="progress"></div>

<script>
  let i = 0;

  function count() {
    for (let j = 0; j < 1e6; j++) {
      i++;
      // Put the current I value in 
       
// (we'll talk about innerHTML in a later section, so you should be able to read this line of code.) progress.innerHTML = i; } } count();
</script> Copy the code

When run, you will see that the I value is displayed only after the entire counting process is complete.

It is much better to use setTimeout to split tasks so that changes can be observed between runs:

<div id="progress"></div>

<script>
  let i = 0;

  function count() {

    // Complete one part at a time (*)
    do {
      i++;
      progress.innerHTML = i;
    } while (i % 1e3! =0);

    if (i < 1e9) {
      setTimeout(count, 0);
    }

  }

  count();
</script>
Copy the code

Now you can observe the growth of I in

.