Javascript prevent multiple setinterval timers from running at the same time. Timers in Javascript (setInterval, setTimeout). setTimeout with zero delay

Source: http://learn.javascript.ru/settimeout-setinterval

Almost all JavaScript implementations have an internal timer scheduler that allows you to schedule a function to be called after a specified period of time.

In particular, this feature is supported in browsers and in the Node.JS server.

setTimeout

Syntax:

var timerId = setTimeout(func/code, delay[, arg1, arg2...])

Parameters:

  • func/code
    • A function or line of code to be executed.
    • The string is maintained for compatibility and is not recommended.
  • delay
    • Latency in milliseconds, 1000 milliseconds equals 1 second.
  • arg1, arg2…
    • Arguments to pass to the function. Not supported in IE9-.
    • The function will be executed after the time specified in the delay parameter.

For example, the following code will trigger alert("Hello") after one second:

function func()( alert("Hello" ); ) setTimeout(func, 1000 );

If the first argument is a string, then the interpreter creates anonymous function from this line.

That is, this entry works exactly the same:

SetTimeout("alert("Hello")" , 1000 );

Use anonymous functions instead:

SetTimeout( function()( alert("Hello" ) ), 1000 );

Parameters for function and context

In all modern browsers Given IE10, setTimeout allows you to specify function parameters.

The example below will display "Hello, I'm Vasya" everywhere except IE9-:

function sayHi (who)( alert("Hi, I'm " + who); ) setTimeout(sayHi, 1000, "Vasya" );

...However, in most cases we need support from the old IE, and it does not allow you to specify arguments. Therefore, in order to transfer them, they wrap the call in an anonymous function:

function sayHi (who)( alert("Hi, I'm " + who); ) setTimeout( function()( sayHi("Vasya")), 1000);

Calling setTimeout does not pass the this context.

In particular, calling an object method via setTimeout will work in the global context. This may lead to incorrect results.

For example, let's call user.sayHi() after one second:

function User (id) function()( alert(this .id); ); ) var user = new User(12345); setTimeout(user.sayHi, 1000 ); // expected 12345, but will output "undefined"

Since setTimeout will run the user.sayHi function in the global context, it will not have access to the object via this .

In other words, these two calls to setTimeout do the same thing:

// (1) one line setTimeout(user.sayHi, 1000 ); // (2) the same thing in two lines var func = user.sayHi; setTimeout(func, 1000 );

Fortunately, this problem is also easily solved by creating an intermediate function:

function User (id)( this .id = id; this .sayHi = function()( alert(this .id); ); ) var user = new User(12345); setTimeout( function()( user.sayHi(); ), 1000 );

A wrapper function is used to pass arguments cross-browser and preserve execution context.

Cancellation of execution

The setTimeout function returns a timerId that can be used to cancel the action.

Syntax:

ClearTimeout(timerId)

In the following example we set a timeout and then delete (changed our mind). As a result, nothing happens.

var timerId = setTimeout( function()( alert(1) ), 1000 ); clearTimeout(timerId);

setInterval

The setInterval method has a syntax similar to setTimeout.

var timerId = setInterval(func/code, delay[, arg1, arg2...])

The meaning of the arguments is the same. But, unlike setTimeout , it does not run the function once, but repeats it regularly at a specified time interval. You can stop execution by calling:

ClearInterval(timerId)

The following example, when run, will display a message every two seconds until you click the Stop button:

<input type ="button" onclick ="clearInterval(timer)" value ="Stop" > !} <script > var i = 1 ; var timer = setInterval( function()( alert(i++) ), 2000 );script >

Queuing and overlaying calls in setInterval

The call setInterval(function, delay) causes the function to execute at the specified time interval. But there is a subtlety here.

In fact, the pause between calls is less than the specified interval.

For example, let's take setInterval(function() ( func(i++) ), 100) . It executes func every 100ms, incrementing the counter each time.

In the picture below, the red block is the execution time of func . Interblock time is the time between function runs and is less than the set delay!

That is, the browser initiates the launch of the function neatly every 100ms, without taking into account the execution time of the function itself.

It happens that the execution of a function takes longer than the delay. For example, the function is complex, but the delay is small. Or the function contains alert / confirm / prompt statements that block the execution thread. This is where things start to get interesting.

If a function cannot be launched because the browser is busy, it will be queued and executed as soon as the browser is free.

The image below illustrates what happens for a function that takes a long time to execute.

The function call initiated by setInterval is added to the queue and occurs immediately when possible:

The second launch of the function occurs immediately after the end of the first:

Execution is not queued more than once.

If a function takes longer to execute than several scheduled executions, it will still queue once. So there is no “accumulation” of launches.

In the image below, setInterval tries to execute the function in 200ms and queues the call. At 300ms and 400ms the timer wakes up again, but nothing happens.

Calling setInterval(function, delay) does not guarantee actual delay between executions.

There are cases when the actual delay is greater or less than the specified one. In general, it is not a fact that there will be at least some delay.

Repeating nested setTimeout

In cases where not just regular repetition is needed, but a delay between runs is required, setTimeout is used to re-set each time the function is executed.

Below is an example that issues an alert with 2 second intervals between them.

<input type ="button" onclick ="clearTimeout(timer)" value ="Stop" > !} <script > var i = 1 ; var timer = setTimeout( function run()( alert(i++); timer = setTimeout(run, 2000); ), 2000);script >

The execution timeline will have fixed delays between runs. Illustration for 100ms delay:

Minimum timer delay

The browser timer has the lowest possible latency. It varies from approximately zero to 4ms in modern browsers. In older ones it can be longer and reach 15ms.

According to the standard, the minimum delay is 4ms. So there is no difference between setTimeout(..,1) and setTimeout(..,4) .

The zero-latency behavior of setTimeout and setInterval is browser-specific.

  1. In Opera, setTimeout(.., 0) is the same as setTimeout(.., 4) . It is executed less frequently than setTimeout(.. ,2). This is a feature of this browser.
  2. IN Internet Explorer, zero delay setInterval(.., 0) will not work. This applies specifically to setInterval , i.e. setTimeout(.., 0) works fine.

Actual trigger frequency

Triggering may be much less frequent. In some cases, the delay may not be 4ms, but 30ms or even 1000ms.

Most browsers (desktop ones primarily) continue to execute setTimeout / setInterval even if the tab is inactive. At the same time, a number of them (Chrome, FF, IE10) reduce the minimum timer frequency to 1 time per second. It turns out that the timer will work in the “background” tab, but rarely.

When running on battery power, on a laptop, browsers can also reduce the frequency to execute code less often and save battery power. IE is especially famous for this. The reduction can reach several times, depending on the settings. If the CPU load is too high, JavaScript may not be able to process timers in a timely manner. This will skip some setInterval runs.

Conclusion: a frequency of 4ms is worth focusing on, but you shouldn’t count on it.

Outputting intervals to the console The code that counts the time intervals between calls looks something like this:

var timeMark = new Date; setTimeout( function go()( var diff = new Date - timeMark; // output the next delay to the console instead of the page console .log(diff); // remember the time at the very end, // to measure the delay between calls timeMark = new Date ; setTimeout(go, 100); ), 100 );

The trick is setTimeout(func, 0)

This trick is worthy of entering the annals of JavaScript hacks.

The function is wrapped in setTimeout(func, 0) if you want to run it after the end of the current script.

The thing is that setTimeout never executes the function right away. He only plans its implementation. But the JavaScript interpreter will begin to perform the planned functions only after the current script is executed.

According to the standard, setTimeout cannot execute a function with a delay of 0 anyway. As we said earlier, the delay will usually be 4ms. But the main thing here is that execution in any case will take place after the execution of the current code.

For example:

var result; function showResult()( alert(result); ) setTimeout(showResult, 0 ); result = 2 *2 ; // will print 4

Total

The setInterval(func, delay) and setTimeout(func, delay) methods allow you to run func regularly/once every delay milliseconds.

Both methods return the timer ID. It is used to stop execution by calling clearInterval / clearTimeout .

| | setInterval | setTimeout | || ----------- | ---------- | | Timing | The call is strictly on a timer. If the interpreter is busy, one call is queued. Function execution time is not taken into account, so the time interval from the end of one run to the start of another may vary. | A recursive call to setTimeout is used instead of setInterval where a fixed pause between executions is needed. | | Delay | Minimum delay: 4ms. | Minimum delay: 4ms. | | Browser Features | Latency 0 does not work in IE. | In Opera, zero latency is equivalent to 4ms, and other delays are handled accurately, including non-standard 1ms, 2ms and 3ms. |

JavaScript timeout is a native javascript function that executes a piece of code after a specified time delay (in milliseconds). This can be useful when you need to display a popup after the user has spent some time on your page. Or do you want the effect when you hover over an element to start only after some time has passed? This way, you can avoid unintentionally triggering an effect if the user hovered over it accidentally.

Simple setTimeout example

To demonstrate how this feature works, I suggest taking a look at the following demo, in which a pop-up window appears two seconds after clicking the button.

View demo

Syntax

The MDN documentation provides the following syntax for setTimeout:

var timeoutID = window.setTimeout(func, ); var timeoutID = window.setTimeout(code, );

  • timeoutID – a numeric id that can be used in combination with clearTimeout() to disable the timer;
  • func – function to be executed;
  • code ( in alternative syntax) – line of code to be executed;
  • delay – the duration of the delay in milliseconds, after which the function will be launched. The default value is 0.

setTimeout vs window.setTimeout

The above syntax uses window.setTimeout . Why?

In fact, setTimeout and window.setTimeout are practically the same function. The only difference is that in the second expression we use the setTimeout method as a property of the global window object.

Personally, I think this just makes the code much more complicated. If we defined alternative method A JavaScript timeout that could be found and returned in priority would then run into even more problems.

IN this manual I don't want to mess with the window object, but in general, it's up to you to decide what syntax you should use.

Examples of use

This could be the name of the function:

function explode())( alert("Boom!"); ) setTimeout(explode, 2000);

Variable that refers to the function:

var explode = function())( alert("Boom!"); ); setTimeout(explode, 2000);

Or an anonymous function:

setTimeout(function())( alert("Boom!"); ), 2000);

  • Such code is poorly understood and, therefore, will be difficult to modernize or debug;
  • It involves the use of the eval() method, which could be a potential vulnerability;
  • This method is slower than others because it needs to run JavaScript interpreter.

Also note that we are using the JavaScript timeout alert method to test the code.

Passing parameters to setTimout

In the first ( besides, cross-browser) option, we pass parameters to the callback function, executed using setTimeout.

In the following example, we extract a random greeting from the greetings array and pass it as a parameter to the greet() function, which is executed by setTimeout with a delay of 1 second:

function greet(greeting)( console.log(greeting); ) function getRandom(arr)( return arr; ) var greetings = ["Hello", "Bonjour", "Guten Tag"], randomGreeting = getRandom(greetings); setTimeout(function())( greet(randomGreeting); ), 1000);

View demo

Alternative method

In the syntax given at the beginning of the article, there is another method by which you can pass parameters to the callback function executed by JavaScript timeout. This method implies the output of all parameters following the delay.

Based on the previous example, we get:

setTimeout(greet, 1000, randomGreeting);

This method will not work in IE 9 and below where passed parameters are regarded as undefined . But to solve this problem MDN has a special polyfill.

Related problems and “this”

The code executed by setTimeout runs separately from the function that called it. Because of this, we are faced with certain problems, the solution to which can be used keyword this.

var person = ( firstName: "Jim", introduce: function())( console.log("Hi, I"m " + this.firstName); ) ); person.introduce(); // Outputs: Hi, I" m Jim setTimeout(person.introduce, 50); // Outputs: Hi, I"m undefined

The reason for this output is that in the first example this points to the person object, and in the second example it points to the global window object, which is missing the firstName property.

To get rid of this inconsistency, you can use several methods:

Force this to be set

This can be done using bind(), a method that creates new feature, which when called uses a specific value as the value of the this key. In our case, the specified person object. This ultimately gives us:

setTimeout(person.introduce.bind(person), 50);

Note: the bind method was introduced in ECMAScript 5, which means it will only work in modern browsers. In others, when you use it you will receive a runtime error JavaScript "function timeout error".

Use library

Many libraries include built-in functions needed to solve the this problem. For example, the jQuery.proxy() method. It takes a function and returns a new one that will always use a specific context. In our case, the context will be:

setTimeout($.proxy(person.introduce, person), 50);

View demo

Disabling the timer

The return value of setTimeout is a numeric id that can be used to disable the timer using the clearTimeout() function:

var timer = setTimeout(myFunction, 3000); clearTimeout(timer);

Let's see it in action. In the following example, if you click on the button " Start countdown", the countdown will begin. After it is completed, the kittens will get theirs. But if you press the button Stop countdown", the JavaScript timeout will be stopped and reset.

View example

Let's sum it up

setTimeout is an asynchronous function, which means that the resulting call to this function goes into a queue and will be executed only after all other actions on the stack have completed. It cannot run simultaneously with other functions or a separate thread.

It is extremely important to understand how JavaScript timers work. Often their behavior does not match our intuitive understanding of multithreading, and this is due to the fact that in reality they are executed in a single thread. Let's look at four functions with which we can manage timers:

  • var id = setTimeout(fn, delay); - Creates a simple timer that will call given function after a specified delay. The function returns a unique ID with which the timer can be paused.
  • var id = setInterval(fn, delay); - Similar to setTimeout, but continuously calls the function at a specified interval (until stopped).
  • clearInterval(id);, clearTimeout(id); - Accepts a timer ID (returned by one of the functions described above) and stops execution of callback"a.
The main idea to consider is that the accuracy of the timer delay period is not guaranteed. To begin with, the browser executes all asynchronous JavaScript events in one thread (such as mouse clicks or timers) and only at the time when it is the turn of that event. This is best demonstrated by the following diagram:

There's quite a lot of information to take in in this figure, but understanding it will give you a deeper understanding of how JavaScript asynchrony works. This chart represents time vertically in milliseconds, the blue blocks show blocks of JavaScript code that were executed. For example, the first block is executed on average in 18ms, a mouse click blocks execution for about 11ms, etc.

JavaScript can only execute one chunk of code (due to the single-threaded nature of execution), each of which blocks the execution of other asynchronous events. This means that when an asynchronous event occurs (such as a mouse click, a timer call, or the completion of an XMLHttp request), it is added to a queue and executed later (implementation varies by browser, of course, but let's agree to call it a "queue") .

To begin with, let’s imagine that two timers start inside a JavaScript block: setTimeout with a delay of 10ms and setInterval with the same delay. Depending on when the timer starts, it will fire at the moment when we have not yet completed the first block of code. Note, however, that it does not fire immediately (this is not possible due to single threading). Instead, the deferred function is queued and executed at the next available moment.

Also, during the execution of the first JavaScript block, a mouse click occurs. The handler for this asynchronous event (and it is asynchronous because we cannot predict it) cannot be executed directly at this moment, so it also ends up in a queue, like the timer.

After the first block of JavaScript code has been executed, the browser asks the question, “What is waiting to be executed?” In this case, the mouse click handler and timer are in a waiting state. The browser selects one of them (the click handler) and executes it. The timer will wait for the next available chunk of time in the execution queue.

Note that while the mouse click handler is executing, the first interval-callback fires. Just like timer-callback, it will be queued. However, note that when interval fires again (while the timer-callback is running), it will be removed from the queue. If every interval-callback" were queued while a large piece of code was executing, it would result in a bunch of functions waiting to be called, with no delay periods between them finishing execution. Instead, browsers tend to wait until there are no more functions left. in the queue before adding another one to the queue.

Thus, we can observe the case when the third firing of the interval-callback coincides with the moment when it is already executed. This illustrates an important point: intervals don't care what is currently running, they will be added to the queue without regard to the delay period between executions.

Finally, after the second interval-callback completes, we will see that there is nothing left for the JavaScript engine to execute. This means that the browser again waits for new asynchronous events to appear. This will happen at the 50ms mark, where interval-callback will work again. At this point there will be nothing to block it, so it will work immediately.

Let's look at an example that nicely illustrates the difference between setTimeout and setInterval.
setTimeout(function())( /* Some long block of code... */ setTimeout(arguments.callee, 10); ), 10); setInterval(function())( /* Some long block of code... */ ), 10);
These two options are equivalent at first glance, but in reality they are not. Code using setTimeout will always have a delay of at least 10ms after the previous call (it can be more, but never less), while code using setInterval will tend to be called every 10ms, regardless of when the previous call occurred.

Let's summarize everything said above:
- JavaScript engines use a single-threaded environment, transforming asynchronous events into a queue awaiting execution,
- The setTimeout and setInterval functions are executed fundamentally differently in asynchronous code,
- If the timer cannot be executed in at the moment, it will be delayed until the next execution point (which will be longer than the desired delay),
- Intervals (setInterval) can be executed one after another without delay if their execution takes longer than the specified delay.

All this is extremely important information for development. Knowing how the JavaScript engine works, especially with a lot of asynchronous events (which often happens), lays a great foundation for building advanced applications.

In programming in scripting languages, there is periodically a need to create a pause - to pause the execution of the program for a while, and then continue working. For example, in VBS and PHP scripts the following methods are possible:

VBS: wscript.sleep 1500 (stop for 1.5 seconds)

PHP: sleep(10); (stop for 10 seconds)

During such pauses, the runtime system (PHP or VBS) does nothing. A developer trying to intuitively use something similar in Javascript will be unpleasantly surprised. A typical error when trying to create a pause in Javascript looks like this:

Function badtest() ( for (var i=1; i< 10; i++) { window.setTimeout("document.getElementById("test1").value += " + i, 900) } }

Do you think that when, during the cycle, the turn comes to drawing the next number, your setTimeout will honestly stop Javascript from working, wait 0.9 seconds, add the required number to the end of the input field and then continue working. But in reality this is not the case: setInterval And setTimeout In Javascript, only the action (or function) specified in parentheses is deferred. In our example, the following will happen:

  1. i = 1;
  2. delay adding the number "1" to the input field by 0.9 seconds;
  3. immediately After setting this problem, the cycle goes further: i=2;
  4. delay adding the number "2" to the input field by 0.9 seconds;

Immediately means, for example, 1 ms (that is, disproportionately small compared to 900 ms): the loop will do its work almost instantly, creating several deferred tasks from the same point in time. This means that all pending “drawing” tasks will be completed at almost the same time, without pauses between adding new numbers. The cycle starts; everything freezes for 0.9 s; and shirr - all the numbers are shot in a row one after another.

How to use it correctly in such a case? setTimeout? It's complicated. You'll have to call the function recursively(from within the function the same function), and so that this process is not endless, set a stopping condition (for example, the size of the number to be printed):

Function welltest() ( if (i< 9) { document.getElementById("test2").value += ++i window.setTimeout("welltest()", 400) } }

And another variable i you will have to initialize outside the function - for example, like this:

Now everything works as it should (we reduced the delay time from 0.9 s to 0.4 s). But for such tasks it is more logical to not use setTimeout A setInterval(although this will require two functions):

Function besttest() ( window.i = 0 window.timer1 = window.setInterval("draw()", 400) ) function draw() ( document.getElementById("test3").value += ++i if (i >= 9) clearInterval(window.timer1) )

Feature of the Javascirpt method setInterval the fact that it does not go away “by itself”, it must be stopped using a special method clearInterval. And to make it clear what exactly to stop, the task for the deferred action is assigned a special identifier - a timer: window.timer1 = window.setInterval(...) .

Identifiers can also be assigned to tasks created by the method setTimeout. All timer IDs must be distinct from each other (unique within the current browser window). Then you can create several different tasks in the window that use deferred actions, and these tasks will be executed in parallel (sort of simultaneously, if the computer has enough resources), which is basically impossible in PHP or VBS.

Here is an example of a page with several Javascript timers running simultaneously: setinterval.htm(Javascript functions in file setinterval.js). All page timers (except menu) can be stopped using the Esc key. All example timers rely on "natural" (rather than abstract) i++) countdown – time or distance. All “clocks” are specially desynchronized (for clarity). Distance-dependent timers are used in the “indicator” and in the drop-down (“pull-out”) menu.

Dropdown menu

Our sliding menu is actually sliding (from under the “header”): gaps are specially left between the elements so that you can see how it slides out. Unexpectedly, it turned out that we could not make equally smooth exits for lists of different lengths - probably due to low computer performance ( AMD Athlon 999 MHz).

It is quite obvious that for beauty and harmony it is necessary that the lists of different menu items appear at the same time. That is, longer lists should drop out at a higher speed, shorter ones at a lower speed. It would seem that it could be implemented like this:

  1. We set the total “departure” time, for example, to 200 ms.
  2. If the dropdown list has a height of 20 px, it is obvious that we can move it down one pixel per 10 ms interval - and then in 200 ms the entire list will come out.
  3. If the dropdown is 40px high, to fit in the same amount of time we have to move it down one pixel every 5ms.

By this logic, if the dropdown list is 200px high, we should move it down one pixel every 1ms. But such speed does not work on our computer - the browser simply does not have time to draw the new position of the list in one millisecond. Yes. Javascript manages to count (what is there to count?), but the browser (Firefox) does not have time to display. Typical situation for the web.

Therefore, it is possible to more or less equalize the time of leaving the menu only with the help of crutches, and it is still unclear how this will work on a faster computer. But we should count on the slowest one, right? The algorithm (without taking into account the speed of the computer) turns out something like this:

  1. Set the total time for checking out the list: time = 224 (ms).
  2. We set the minimum time for one interval in the cycle: delay = 3 (ms).
  3. Set the minimum step for moving the list: offset = 1 (px).
  4. We change all this depending on the height of the list: 1) increase the delay (interval) time in inverse proportion to the height and directly proportional to the total time time (at a height of 224 the coefficient is 1); 2) if the height is greater than 40 px, increase the minimum step in proportion to the height. The constant "40" was obtained experimentally for the slowest computer. Tests on a Pentium 4 CPU 2.53GHz computer revealed exactly the same number - 40. Otherwise, timers go out of order, lists go out of step.

Now the lists are more or less coming out. For more or less similar time. On the page setinterval.htm.

And here comes Bruce:

Function slide_do(obj, maxtop, offset) ( if (getTopLeft(obj).top< maxtop) { obj.style.top = getTopLeft(obj).top + offset } else { if (obj && obj.timer1) { clearInterval(obj.timer1) obj.timer1 = null } } }

The function itself, which pushes nested lists out of the menu, is, as we can see, very simple. All that remains is to run it with something like this line:

Ts.timer1 = setInterval(function())(slide_do(ts, maxtop, offset)), delay)

Well, before starting, just calculate all these maxtop and offset, and also place the list in the mintop position. What does the “preliminary” function do? slide() 40 lines in size. And all together - in a file setinterval.js. Yes, and this crap won't work at all without the included styles file


Top