Merge pull request #366 from brentguf/timers

Timers
This commit is contained in:
Ilya Kantor 2018-02-06 12:52:51 +03:00 committed by GitHub
commit 37f1936622
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 28 additions and 28 deletions

View file

@ -15,7 +15,7 @@ function count() {
if (i == 1000000000) { if (i == 1000000000) {
alert("Done in " + (Date.now() - start) + 'ms'); alert("Done in " + (Date.now() - start) + 'ms');
cancelInterval(timer); clearInterval(timer);
} }
} }

View file

@ -6,14 +6,14 @@ importance: 5
In the code below there's a `setTimeout` call scheduled, then a heavy calculation is run, that takes more than 100ms to finish. In the code below there's a `setTimeout` call scheduled, then a heavy calculation is run, that takes more than 100ms to finish.
When the scheduled function will run? When will the scheduled function run?
1. After the loop. 1. After the loop.
2. Before the loop. 2. Before the loop.
3. In the beginning of the loop. 3. In the beginning of the loop.
What `alert` is going to show? What is `alert` going to show?
```js ```js
let i = 0; let i = 0;

View file

@ -91,7 +91,7 @@ let timerId = setTimeout(...);
clearTimeout(timerId); clearTimeout(timerId);
``` ```
In the code below we schedule the function and then cancel it (changed our mind). As a result, nothing happens: In the code below, we schedule the function and then cancel it (changed our mind). As a result, nothing happens:
```js run no-beautify ```js run no-beautify
let timerId = setTimeout(() => alert("never happens"), 1000); let timerId = setTimeout(() => alert("never happens"), 1000);
@ -101,7 +101,7 @@ clearTimeout(timerId);
alert(timerId); // same identifier (doesn't become null after canceling) alert(timerId); // same identifier (doesn't become null after canceling)
``` ```
As we can see from `alert` output, in a browser the timer identifier is a number. In other environments, that can be something else. For instance, Node.JS returns a timer object with additional methods. As we can see from `alert` output, in a browser the timer identifier is a number. In other environments, this can be something else. For instance, Node.JS returns a timer object with additional methods.
Again, there is no universal specification for these methods, so that's fine. Again, there is no universal specification for these methods, so that's fine.
@ -109,7 +109,7 @@ For browsers, timers are described in the [timers section](https://www.w3.org/TR
## setInterval ## setInterval
Method `setInterval` has the same syntax as `setTimeout`: The `setInterval` method has the same syntax as `setTimeout`:
```js ```js
let timerId = setInterval(func|code, delay[, arg1, arg2...]) let timerId = setInterval(func|code, delay[, arg1, arg2...])
@ -154,11 +154,11 @@ let timerId = setTimeout(function tick() {
}, 2000); }, 2000);
``` ```
The `setTimeout` above schedules next call right at the end of the current one `(*)`. The `setTimeout` above schedules the next call right at the end of the current one `(*)`.
Recursive `setTimeout` is more flexible method than `setInterval`. This way the next call may be scheduled differently, depending on the results of the current one. The recursive `setTimeout` is a more flexible method than `setInterval`. This way the next call may be scheduled differently, depending on the results of the current one.
For instance, we need to write a service that every 5 seconds sends a request to server asking for data, but in case the server is overloaded, it should increase the interval to 10, 20, 40 seconds... For instance, we need to write a service that sends a request to the server every 5 seconds asking for data, but in case the server is overloaded, it should increase the interval to 10, 20, 40 seconds...
Here's the pseudocode: Here's the pseudocode:
```js ```js
@ -205,23 +205,23 @@ For `setInterval` the internal scheduler will run `func(i)` every 100ms:
![](setinterval-interval.png) ![](setinterval-interval.png)
Did you notice?... Did you notice?
**The real delay between `func` calls for `setInterval` is less than in the code!** **The real delay between `func` calls for `setInterval` is less than in the code!**
That's natural, because the time is taken by `func` execution "consumes" a part of the interval. That's normal, because the time taken by `func`'s execution "consumes" a part of the interval.
It is possible that `func` execution turns out to be longer than we expected and takes more than 100ms. It is possible that `func`'s execution turns out to be longer than we expected and takes more than 100ms.
In this case the engine waits for `func` to complete, then checks the scheduler and if the time is up, then runs it again *immediately*. In this case the engine waits for `func` to complete, then checks the scheduler and if the time is up, runs it again *immediately*.
In the edge case, if the function always executes longer than `delay` ms, then the calls will happen without pause at all. In the edge case, if the function always executes longer than `delay` ms, then the calls will happen without a pause at all.
And here is the picture for recursive `setTimeout`: And here is the picture for the recursive `setTimeout`:
![](settimeout-interval.png) ![](settimeout-interval.png)
**Recursive `setTimeout` guarantees the fixed delay (here 100ms).** **The recursive `setTimeout` guarantees the fixed delay (here 100ms).**
That's because a new call is planned at the end of the previous one. That's because a new call is planned at the end of the previous one.
@ -258,11 +258,11 @@ The first line "puts the call into calendar after 0ms". But the scheduler will o
### Splitting CPU-hungry tasks ### Splitting CPU-hungry tasks
There's a trick to split CPU-hungry task using `setTimeout`. There's a trick to split CPU-hungry tasks using `setTimeout`.
For instance, syntax highlighting script (used to colorize code examples on this page) is quite CPU-heavy. To highlight the code, it performs the analysis, creates many colored elements, adds them to the document -- for a big text that takes a lot. It may even cause the browser to "hang", that's unacceptable. For instance, a syntax-highlighting script (used to colorize code examples on this page) is quite CPU-heavy. To highlight the code, it performs the analysis, creates many colored elements, adds them to the document -- for a big text that takes a lot. It may even cause the browser to "hang", which is unacceptable.
So we can split the long text to pieces. First 100 lines, then plan another 100 lines using `setTimeout(...,0)`, and so on. So we can split the long text into pieces. First 100 lines, then plan another 100 lines using `setTimeout(...,0)`, and so on.
For clarity, let's take a simpler example for consideration. We have a function to count from `1` to `1000000000`. For clarity, let's take a simpler example for consideration. We have a function to count from `1` to `1000000000`.
@ -286,7 +286,7 @@ function count() {
count(); count();
``` ```
The browser may even show "the script takes too long" warning (but hopefully won't, the number is not very big). The browser may even show "the script takes too long" warning (but hopefully it won't, because the number is not very big).
Let's split the job using the nested `setTimeout`: Let's split the job using the nested `setTimeout`:
@ -319,15 +319,15 @@ We do a part of the job `(*)`:
1. First run: `i=1...1000000`. 1. First run: `i=1...1000000`.
2. Second run: `i=1000001..2000000`. 2. Second run: `i=1000001..2000000`.
3. ...and so on, the `while` checks if `i` is evenly divided by `100000`. 3. ...and so on, the `while` checks if `i` is evenly divided by `1000000`.
Then the next call is scheduled in `(*)` if we're not done yet. Then the next call is scheduled in `(*)` if we're not done yet.
Pauses between `count` executions provide just enough "breath" for the JavaScript engine to do something else, to react on other user actions. Pauses between `count` executions provide just enough "breath" for the JavaScript engine to do something else, to react to other user actions.
The notable thing is that both variants: with and without splitting the job by `setTimeout` -- are comparable in speed. There's no much difference in the overall counting time. The notable thing is that both variants -- with and without splitting the job by `setTimeout` -- are comparable in speed. There's no much difference in the overall counting time.
To make them closer let's make an improvement. To make them closer, let's make an improvement.
We'll move the scheduling in the beginning of the `count()`: We'll move the scheduling in the beginning of the `count()`:
@ -356,14 +356,14 @@ function count() {
count(); count();
``` ```
Now when we start to `count()` and know that we'll need to `count()` more -- we schedule that immediately, before doing the job. Now when we start to `count()` and know that we'll need to `count()` more, we schedule that immediately, before doing the job.
If you run it, easy to notice that it takes significantly less time. If you run it, it's easy to notice that it takes significantly less time.
````smart header="Minimal delay of nested timers in-browser" ````smart header="Minimal delay of nested timers in-browser"
In the browser, there's a limitation of how often nested timers can run. The [HTML5 standard](https://www.w3.org/TR/html5/webappapis.html#timers) says: "after five nested timers..., the interval is forced to be at least four milliseconds.". In the browser, there's a limitation of how often nested timers can run. The [HTML5 standard](https://www.w3.org/TR/html5/webappapis.html#timers) says: "after five nested timers, the interval is forced to be at least four milliseconds.".
Let's demonstrate what it means by the example below. The `setTimeout` call in it re-schedules itself after `0ms`. Each call remembers the real time from the previous one in the `times` array. What the real delays look like? Let's see: Let's demonstrate what it means with the example below. The `setTimeout` call in it re-schedules itself after `0ms`. Each call remembers the real time from the previous one in the `times` array. What do the real delays look like? Let's see:
```js run ```js run
let start = Date.now(); let start = Date.now();