This article has participated in the call for good writing activities, click to view: back end, big front end double track submission, 20,000 yuan prize pool waiting for you to challenge!
Why does setTimeout not work on time?
This paper starts from the execution time of a code to understand the basic use of setTimeout, analyzes how the browser implements setTimeout in a simple way, and then explores why the timer setTimeout cannot be executed on time.
Start with the execution time of a piece of code
Take a look at the execution time of the following code:
let startTime = +new Date(a);setTimeout(() = > {
let endTime = +new Date(a);console.log(endTime - startTime);
}, 20);
for (let i = 0; i < 90000000; i++) { }
Copy the code
When you first see this code, do you think that setTimeout will be executed after 20ms, and console.log() will print 20?
I also thought it would print 20 at first, even if it took the JS engine 1ms or 2ms to calculate the endtime-startTime, it would print 21 or 22.
The result was far from what I expected. When I put the code on the browser console, it looked something like this:
Console.log () prints a result of 80, which is a lot different from the setTimeout I set for 20ms.
So why doesn’t setTimeout execute on time? This aroused my desire to explore, so I went over the setTimeout WebAPI related knowledge, sorted out the output of this article.
setTimeout
Basic use of
The setTimeout() method is used to call a function or evaluate an expression after a specified number of milliseconds.
Simply put, setTimeout() is a timer that specifies how many milliseconds later a function should be executed.
As follows:
setTimeout(function() {
console.log('Print in 1 second');
}, 1000)
Copy the code
Execute on the console as shown below, which executes the code inside the function after 1000ms, that is, printing 1 second after printing the text.
setTimeout
The return value of the
I get it, but what about the 170 in front of it?
SetTimeout returns an integer indicating the number of the timer, which can be used to cancel the timer.
This number means that although I set setTimeout to execute the code after the specified time, I can cancel the timer before the time is up. The way to cancel the timer is clearTimeout(), as follows:
let timer = setTimeout(function() {
console.log('Print in 1 second');
}, 1000);
clearTimeout(timer);
Copy the code
To cancel the timer by passing the timer number to clearTimeout, you can see that the asynchronous task in setTimeout did not execute.
setTimeout
The third parameter of
SetTimeout also has a third argument, not just three. It can have multiple arguments, as follows:
setTimeout(function(a, b, c, d) {
console.log(a, b, c, d)
}, 100.1.2.3.4)
Copy the code
SetTimeout starts with the third argument, which can be passed in as arguments to an anonymous function.
Use the third parameter to solve a classic interview question that everyone has encountered:
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i)
}, 100)}Copy the code
In this case, it prints five fives after 100ms. To print 0, 1, 2, 3, and 4 after 100ms, we can use the third parameter:
for (var i = 0; i < 5; i++) {
setTimeout(function (j) {
console.log(j)
}, 100, i)
}
Copy the code
setTimeout
In thethis
The this in a callback set with setTimeout is not intuitive.
If the callback delayed by setTimeout is a method of an object, the this keyword in that method points to the global environment, not the object at which it was defined.
As follows:
var name = "I'm a global variable.";
var user = {
name: "I'm a variable in an object.".showName: function () {
console.log(this.name); }}setTimeout(user.showName, 1000);
Copy the code
The output here is the I am global variable, because when this code is compiled, this in the execution context will be set to global Window, or undefined if it is in strict mode.
This problem can be solved as follows:
// The first is to execute user.showname in an anonymous function
/ / function function
setTimeout(function () {
user.showName();
}, 1000);
// or arrow functions
setTimeout(() = > user.showName(), 1000);
// The second way is to use call/apply/bind to change the this pointer of showName
setTimeout(user.showName.call(user), 1000)
Copy the code
This in setTimeout also has the following cases:
var name = "I'm a global variable.";
var user = {
name: "I'm a variable in an object.".showName: function () {
setTimeout(function () {
console.log(this.name);
})
}
}
user.showName();
Copy the code
In this case, setTimeout is equivalent to a nested function. This in a nested function does not inherit the this value of the outer function. There are many solutions to this problem, such as: Use arrow functions, set a variable to cache this, pass in a third argument, use closures, change the reference to this using call/apply/bind, etc.
How does the browser implement itsetTimeout
?
Having covered the basics of using setTimeout, I’d like to explore how browsers implement setTimeout.
If you want to explore this, it is necessary to know the browser event loop mechanism. I have written about the event loop in the previous article, if you are interested in it, you can check it out:
Event Loop Is an Event Loop.
Different callback execution times: Macro and Micro Tasks
Asynchronous Programming Promises: From Use to Handwritten Implementation (4,200 Words)
The Ultimate solution to Asynchronous programming async/await: Writing asynchronous code in a synchronous way
As we know, browsers maintain tasks through message queues. To execute an asynchronous task, you need to add the task to the message queue. They need to be called at a specified time interval, but the tasks in the message queue are executed in order, so to ensure that the callbacks can be executed at a specified time, the timer callbacks cannot be added directly to the message queue.
If you want to add timer function on the basis of the event cycle system, how should you design it?
In addition to the normal message queue in Chrome, there is another message queue (delay queue), which maintains a list of tasks that need to be delayed, including timers and some tasks that need to be delayed in Chromium. So when a timer is created in JavaScript, the renderer adds the timer’s callback task to the delay queue.
There is a ProcessDelayTask function in Chrome that is specifically designed to handle delayed execution of tasks. It is executed after processing a task in the message queue. The ProcessDelayTask function calculates due tasks based on initiation time and delay time, and then executes those due tasks in turn. Wait for the due task to complete, then continue the next cycle. In this way, a complete timer is realized.
The timer will return a number. The specific method to cancel is to call the clearTimeout function and pass in the number of the timer to cancel. The internal operation is to find the corresponding task from the delay queue by this number, and then delete it from the queue.
What causes itsetTimeout
What about not being on time?
How to use setTimeout and how to implement setTimeout in the browser have been introduced in the previous section, so we will look at the reasons why setTimeout is not accurate, and we can also tell one or two when others ask.
1. The current task execution time is too long
Callback tasks set by setTimeout are placed in a delay queue and wait for the next execution, which is not immediate; To execute the next task in the message queue, wait until the execution of the current task is complete. If the execution time of the current task is too long, the execution of the timer task will be delayed.
To put it simply, the JS engine will execute the synchronous code before the asynchronous code. If the synchronous code execution takes too long, the asynchronous code execution will be delayed.
The example at the beginning of this article is that setTimeout cannot be executed on time because the previous task execution time is too long.
let startTime = +new Date(a);setTimeout(() = > {
let endTime = +new Date(a);console.log(endTime - startTime);
}, 20);
for (let i = 0; i < 90000000; i++) { }
Copy the code
For (let I = 0; i < 90000000; I++) {}
As you can see, the 90000000 for loop took about 80 + milliseconds (depending on the performance, some computers may only need 40 or 50 milliseconds).
The reason why setTimeout does not execute on time is found in the preceding code.
Consider the following example:
setTimeout(() = > {
console.log(1);
}, 20);
for (let i = 0; i < 90000000; i++) { }
setTimeout(() = > {
console.log(2);
}, 0);
Copy the code
If there is no for loop in the middle, the print order should be 2 and 1, according to the time set by two setTimeout.
What if I put a for loop in the middle?
It’s printed in order 1, 2.
I use this example because I want to show that setTimeout callback tasks are added to the delay queue in sequence. When a task is completed, ProcessDelayTask calculates the expired tasks based on the time of initiation and the delay time, and then executes those expired tasks in sequence.
After executing the previous tasks, both settimeouts in the above example expire, so executing in sequence prints 1 and 2. So in this scenario, setTimeout looks less reliable.
Second, nested callssetTimeout
There is minimum delay4ms
If setTimeout has nested calls, the minimum interval is set to 4ms.
When the second parameter of setTimeout is set to 0 (default for unset, less than 0, and greater than 2147483647), it means to execute immediately or as soon as possible.
But in Chrome it has a setting like this:
If timeout is less than 0, then set timeout to 0. If nesting level is greater than 5, and timeout is less than 4, then set timeout to 4.
The above statement means that if the delay is less than 0, the delay is set to 0. If the timer is nested for more than five times and the delay is less than 4ms, the delay is set to 4ms.
That is, call timer nested in timer function, and the system will judge that the function method is blocked. If the delay time is less than 4ms, the execution time of timer will be extended to 4ms.
As the following code, has been nested calls, the timer behind will have a minimum delay of 4ms
function cb() { setTimeout(cb, 0); }
setTimeout(cb, 0);
Copy the code
Iii. Inactive pagessetTimeout
The minimum execution interval of is1000ms
In addition to the 4ms delay, it is easy to overlook the fact that the minimum timer for inactive pages is greater than 1000ms, that is, if the tag is not currently active, the minimum timer interval is 1000ms, in order to optimize the load consumption of background pages and reduce power consumption.
What is the concept of an inactive page?
To switch the TAB or minimize the browser, you have the following code:
This is a timer that counts from 100 to 0 every 50ms.
let num = 100;
function setTime() {
// Timing of the current second execution
console.log(new Date().getSeconds() + ":" + num);
num ? num-- && setTimeout(() = > setTime(), 50) : "";
}
setTime();
Copy the code
As you can see in the image below, I have minimized the browser for the current 22s, and it only executes a timer for 1s for the next few seconds.
So use the timer setTimeout and pay attention to this detail, you really don’t know until you try it.
Four, the delay time has the maximum value
Chrome, Safari, Firefox, and other browsers use 32 bits to store the delay value. The maximum number of bits that can be stored is 2147483647 milliseconds (about 24.8 days). If the delay value is greater than this value, the overflow will occur. The delay value is set to 0, which causes the timer to be executed immediately.
The following code:
setTimeout(function () {
console.log("Will it be printed in 24.8 days?")},2147483648);
Copy the code
If the setting time is greater than 2147483647, it will be executed immediately.
sincesetTimeout
Can it still work if it’s not on time?
The timer setTimeout can be used naturally. Its function is to specify the number of milliseconds after the execution of a function. However, setTimeout may be inaccurate for various reasons, so it is not suitable to use setTimeout for some requirements of high time accuracy. For example, using setTimeout to implement JS animations is not a good idea.
Comparatively speaking, setTimeout can be used for scenes that do not require high accuracy of time.
What can replace itsetTimeout
?
Since setTimeout has a lot of congenital shortcomings in timeliness, other solutions should be adopted for some demands with high time accuracy.
For example, if you want to use JS for animation, the function requestAnimationFrame is a good choice.
RequestAnimationFrame works better than setTimeout for the following reasons:
- use
requestAnimationFrame
There is no need to set a specific time;- It provides a native API to perform the effects of an animation, which will be performed in one frame
16ms
) interval to perform relevant actions according to the browser selection. setTimeout
The task is executed at a certain time interval, and it is not executed until that time interval, so that the browser has no way to optimize automatically
- It provides a native API to perform the effects of an animation, which will be performed in one frame
requestAnimationFrame
The callback function is executed before the page is refreshed. It follows the refresh rate of the screen, ensuring that each refresh interval is executed only once.- If the page is not active,
requestAnimationFrame
Rendering is also stopped to keep the page running smoothly and to save the main thread from executing functions.
An Event Loop interview question
In the previous article, Event Loops: Do you know their print order? In the comments section, a friend posted another Event Loop question as follows:
function func1() {
console.log('func1 start');
return new Promise(resolve= > {
resolve('OK'); })}function func2() {
console.log('func2 start');
return new Promise(resolve= > {
setTimeout(() = > {
resolve('OK');
}, 10)})}console.log(1);
setTimeout(async() = > {console.log(2);
await func1();
console.log(3);
}, 20);
for (let i = 0; i < 90000000; i++) { } / / about 80 ms
console.log(4);
func1().then(() = > {
console.log(5);
})
func2().then(() = > {
console.log(6);
})
setTimeout(() = > {
console.log(7)},0);
console.log(8);
Copy the code
You can do it. It’s fun.
conclusion
setTimeout()
Method is used to call a function or evaluate an expression after a specified number of milliseconds.- Browsers do this by maintaining a delay queue
setTimeout
, there is aProcessDelayTask
Function to check expired tasks and then execute them in sequence. - Lead timer
setTimeout
There are four reasons for unpunctuality:- JS is a single-thread task. If the execution time of the current task is too long, the timer task will be delayed.
- if
setTimeout
If there are nested calls and more than five times, the minimum interval is set to4ms
. - Inactive pages,
setTimeout
The minimum execution interval of is1000ms
. setTimeout
Has the maximum delay time2147483647ms
.
setTimeout
Do not apply to scenarios that require high time accuracyrequestAnimationFrame
To substitute execution.