📜 ⬆️ ⬇️

JavaScript timers: all you need to know

Hello colleagues. A long time ago, an article by H. Rezig was written on Habré on this subject. 10 years have passed, and the topic still needs clarification. Therefore, we offer those interested to read the article by Samer Buna, which not only provides a theoretical overview of the timers in JavaScript (in the context of Node.js), but also the tasks for them.




A few weeks ago, I tweeted the following question from a single interview:
')
“Where is the source code for the setTimeout and setInterval functions? Where would you look for it? You can not google :) "

*** Answer it for yourself, and then read on ***



About half of the responses to this tweet were incorrect. No, the case is NOT CONNECTED with V8 (or other VM) !!! Functions like setTimeout and setInterval , proudly referred to as "JavaScript Timers", are not included in any ECMAScript specification or JavaScript engine implementation. Timer functions are implemented at the browser level, so their implementation differs in different browsers. Also, timers are natively implemented in the Node.js runtime itself.

In browsers, the basic timer functions are related to the Window interface, which is also associated with some other functions and objects. This interface provides global access to all its elements in the main JavaScript scope. That is why the setTimeout function can be performed directly in the browser console.

In Node, timers are part of a global object, which is designed like a Window browser interface. The source code for the timers in Node is shown here .

It may seem to someone that this is just a bad question from an interview - what is the use of knowing this? I, as a JavaScript developer, think so: it is assumed that you should know this, because the opposite may indicate that you do not quite understand how V8 (and other virtual machines) interact with browsers and Node.

Consider a few examples and solve a couple of tasks on timers, let's go?

To run the examples in this article, use the node command. Most of the examples reviewed here appear in my Getting Started with Node.js course at Pluralsight.

Deferred function

Timers are higher-order functions with which you can postpone or repeat the execution of other functions (the timer receives such a function as the first argument).

Here is an example of deferred execution:

 // example1.js setTimeout( () => { console.log('Hello after 4 seconds'); }, 4 * 1000 ); 

In this example, using setTimeout output of the welcome message is delayed by 4 seconds. The second argument to setTimeout is the delay (in ms). I multiply 4 by 1000 to get 4 seconds.

The first argument to setTimeout is a function whose execution will be postponed.
If you execute example1.js with the node command, Node pauses for 4 seconds and then displays a welcome message (followed by an exit).

Please note: the first argument to setTimeout is just a reference to a function . It should not be a built-in function, such as example1.js . Here is the same example without using the built-in function:

 const func = () => { console.log('Hello after 4 seconds'); }; setTimeout(func, 4 * 1000); 

Passing arguments

If the function for which setTimeout is used for delay takes any arguments, then the remaining arguments of the setTimeout function itself (after the 2 that we have already studied) can be used to transfer the values ​​of the arguments to the deferred function.

 // : func(arg1, arg2, arg3, ...) //  : setTimeout(func, delay, arg1, arg2, arg3, ...) 

Here is an example:

 // example2.js const rocks = who => { console.log(who + ' rocks'); }; setTimeout(rocks, 2 * 1000, 'Node.js'); 

The above rocks function, deferred for 2 seconds, takes a who argument, and the call to setTimeout passes the value “Node.js” to it as such a who argument.

When executing example2.js with the node command, the phrase “Node.js rocks” will be displayed in 2 seconds.

Task for timers # 1

So, based on the already studied material on setTimeout , we will display 2 following messages after the corresponding delays.


Restriction

In your solution, you can define only one function that contains built-in functions. This means that multiple setTimeout calls will need to use the same function.

Decision

Here's how I would solve this problem:

 // solution1.js const theOneFunc = delay => { console.log('Hello after ' + delay + ' seconds'); }; setTimeout(theOneFunc, 4 * 1000, 4); setTimeout(theOneFunc, 8 * 1000, 8); 

In my theOneFunc receives the delay argument and uses the value of the given delay argument in the message displayed on the screen. Thus, the function can display different messages depending on the delay value we will tell it.

Then I used theOneFunc in two calls to setTimeout , and the first call works after 4 seconds, and the second after 8 seconds. Both of these calls to setTimeout also get the 3rd argument, representing the delay argument for theOneFunc .

Having executed the file solution1.js with the node command, we will display the requirements of the task, and the first message will appear after 4 seconds, and the second after 8 seconds.

Repeat the function

And what if I asked you to display a message every 4 seconds, indefinitely?
Of course, you can enclose setTimeout in a loop, but the timer API also offers the setInterval function, with which you can program the "eternal" execution of an operation.

Here is an example of setInterval :

 // example3.js setInterval( () => console.log('Hello every 3 seconds'), 3000 ); 

This code will display a message every 3 seconds. If you execute example3.js with the node command, Node will output this command until you forcibly terminate the process (CTRL + C).

Cancel Timers

Since an action is assigned when the timer function is called, this action can also be undone before it is executed.

A call to setTimeout returns a timer ID, and you can use this timer ID when calling clearTimeout to cancel the timer. Here is an example:

 // example4.js const timerId = setTimeout( () => console.log('You will not see this one!'), 0 ); clearTimeout(timerId); 

This simple timer should fire after 0 ms (that is, immediately), but this will not happen, since we capture the timerId value and immediately cancel this timer by calling clearTimeout .

When executing example4.js with the node command, Node prints nothing - the process will simply end immediately.

By the way, Node.js provides another way to set setTimeout with a value of 0 ms. The Node.js setImmediate API has another function called setImmediate , and it basically does the same thing as setTimeout with a value of 0 ms, but in this case, the delay can be omitted:

 setImmediate( () => console.log('I am equivalent to setTimeout with 0 ms'), ); 

The setImmediate function is setImmediate supported in all browsers . Do not use it in the client code.

Along with clearTimeout there is a clearInterval function that does the same thing, but with setInerval calls, and there is also a clearImmediate call.

Timer delay is not a guaranteed item.

Did you notice that in the previous example, when performing an operation with setTimeout after 0 ms, this operation does not occur immediately (after setTimeout ), but only after all the script code has been completely executed (including the clearTimeout call)?

Let me explain this point with an example. Here is a simple call to setTimeout , which should work out in half a second - but this does not happen:

 // example5.js setTimeout( () => console.log('Hello after 0.5 seconds. MAYBE!'), 500, ); for (let i = 0; i < 1e10; i++) { //    } 

Immediately after determining the timer in this example, we synchronously block the runtime environment with a large for loop. The value of 1e10 is 1 with 10 zeros, so the cycle lasts 10 billion processor cycles (in principle, this is how an overloaded processor is simulated). Node can do nothing until this loop ends.

Of course, in practice, doing so is very bad, but this example helps to understand that the setTimeout delay is not a guaranteed, but rather a minimum value . A value of 500 ms means that the delay will last at least 500 ms. In fact, the script will take much more time to display the welcome line on the screen. First, he will have to wait until the blocking cycle is completed.

Task for timers # 2

Write a script that will display the message “Hello World” once a second, but only 5 times. After 5 iterations, the script should display the message “Done”, after which the Node process will end.

Restriction : when solving this problem, setTimeout cannot be called.

Hint : need a counter.

Decision

Here's how I would solve this problem:

 let counter = 0; const intervalId = setInterval(() => { console.log('Hello World'); counter += 1; if (counter === 5) { console.log('Done'); clearInterval(intervalId); } }, 1000); 

I set 0 as the initial value of counter , and then called setInterval , which takes its id.

The deferred function will display a message and each time increase the counter by one. Inside the deferred function, we have an if statement that will check if 5 iterations have passed. After 5 iterations, the program displays “Done” and clears the interval value using the captured intervalId constant. The delay interval is 1000 ms.

"Who" exactly causes deferred functions?

When using the javascript this inside a regular function, like this:

 function whoCalledMe() { console.log('Caller is', this); } 

the value in the this will match the caller . If you define the above function inside the Node REPL, then the global object will call it. If you define a function in the browser console, then the window object will call it.

Let's define a function as a property of an object so that it becomes a little clearer:

 const obj = { id: '42', whoCalledMe() { console.log('Caller is', this); } }; //     : obj.whoCallMe 

Now, when working with the obj.whoCallMe function we will directly use the link to it, the obj object (identified by its id ) will act as the caller:



And now the question: who will be the caller, if you pass the reference to obj.whoCallMe to the obj.whoCallMe call?

 //       ?? setTimeout(obj.whoCalledMe, 0); 

Who is the caller in this case?

The answer will differ depending on where the timer function is performed. In this case, the dependence on who is the caller is simply unacceptable. You will lose control of the caller, since it is up to the implementation of the timer that will determine who in this case calls your function. If you test this code in a Node REPL, then the caller will be a Timeout object:



Note: this is important only when the javascript this used inside normal functions. When using switch functions, the caller should not bother you at all.

Timer Challenge # 3

Write a script that will continuously display the “Hello World” message with varying delays. Start with a one-second delay, then increase it by one second at each iteration. On the second iteration, the delay will be 2 seconds. In the third - three, and so on.

Include a delay in the displayed message. You should get something like this:

Hello World. 1
Hello World. 2
Hello World. 3
...


Restrictions : Variables can only be defined with const. Using let or var is impossible.

Decision

Since the length of the delay in this task is a variable, you cannot use setInterval here, but you can manually adjust the interval execution using setTimeout inside a recursive call. The first function executed with setTimeout will create the next timer, and so on.

Also, since you cannot use let / var , we cannot have a counter to increment the delay on each recursive call; instead, you can use the arguments of the recursive function to increment during a recursive call.

Here's how to solve this problem:

 const greeting = delay => setTimeout(() => { console.log('Hello World. ' + delay); greeting(delay + 1); }, delay * 1000); greeting(1); 

Task for timers # 4

Write a script that will display the “Hello World” message with the same delay structure as in task # 3, but this time in groups of 5 messages, and the groups will contain the main delay interval. For the first group of 5 messages, we select an initial delay of 100 ms, for the next one - 200 ms, for the third one - 300 ms, and so on.

Here’s how this script should work:


According to this principle, the program should work indefinitely.

Include a delay in the displayed message. You should get something like this (without comments):

Hello World. 100 // 100
Hello World. 100 // 200
Hello World. 100 // 300
Hello World. 100 // 400
Hello World. 100 // 500
Hello World. 200 // 700
Hello World. 200 // 900
Hello World. 200 // 1100
...


Restrictions : You can use only setInterval calls (and not setTimeout ) and only ONE if .

Decision

Since we can only work with setInterval calls, here we will need to use recursion, as well as increase the delay of the next setInterval call. In addition, the if will need us to make this happen only after 5 calls to this recursive function.

Here is a possible solution:

 let lastIntervalId, counter = 5; const greeting = delay => { if (counter === 5) { clearInterval(lastIntervalId); lastIntervalId = setInterval(() => { console.log('Hello World. ', delay); greeting(delay + 100); }, delay); counter = 0; } counter += 1; }; greeting(100); 

Thanks to everyone who read it.

Source: https://habr.com/ru/post/426709/


All Articles