官术网_书友最值得收藏!

Creating tasks using timers

So far in this chapter, we've had a look at all the inner workers of the web browser environment, and where the JavaScript interpreter fits in this environment. What does all this have to do with applying concurrency principles to our code? With the knowledge of what's happening under the hood, we have a greater insight into what's happening when a given chunk of our code is run. Particularly, we know what's happening relative to other code chunks; time ordering is a crucial concurrency property.

This being said, let's actually write some code. In this section, we'll use timers to explicitly add tasks to the task queue. We'll also learn when and where the JavaScript interpreter jumps in and starts executing our code.

Using setTimeout()

The setTimeout() function is staple in any JavaScript code. It's used to execute code at some point in the future. New JavaScript programmers often trip over the setTimeout() function because it's a timer. At a set point in the future, say 3 seconds from now, a callback function will be invoked. When we call setTimeout(), we will get the atimer ID in return, which can be cleared later on using clearTimeout(). Here's what the basic usage of setTimeout() looks like:

// Creates a timer that calls our function in no less
// than 300MS. We can use the "console.time()" and the
// "console.timeEnd()" functions to see how long it actually
// takes.
//
// This is typically around 301MS, which isn't at all 
// noticeable by the user, but is unreliable for
// accurately scheduling function calls.
var timer = setTimeout(() => {
    console.timeEnd('setTimeout');
}, 300);

console.time('setTimeout');

Here's the part that's misunderstood by JavaScript newcomers; it's a best effort timer. The only guarantee we have when using setTimeout() is that our callback function will never be called sooner than the allotted time that we pass it. So if we said call this function in 300 milliseconds, it'll never call it in 275 milliseconds. Once the 300 milliseconds have elapsed, a new task is queued. If there's nothing waiting in line before this task, the callback is run right on time. Even if there are a few things in the queue in front of it, the effects are hardly noticeable—it appears to run at the correct time.

But as we've seen, JavaScript is single threaded and run-to-completion. This means that once the JavaScript interpreter starts, it doesn't stop until it's finished; even if there's a task waiting for a timer event callback. So, it's entirely possible that even though we asked the timer to execute the callback in 300 milliseconds, it executes it in 500 milliseconds. Let's take a look at an example to see how this is possible:

// Be careful, this function hogs the CPU...
function expensive(n = 25000) {
    var i = 0;
    while (++i < n * n) {}
    return i;
}

// Creates a timer, the callback uses
// "console.timeEnd()" to see how long we
// really waited, compared to the 300MS
// we were expecting.
var timer = setTimeout(() => {
    console.timeEnd('setTimeout');
}, 300);

console.time('setTimeout');

// This takes a number of seconds to
// complete on most CPUs. All the while, a
// task has been queued to run our callback
// function. But the event loop can't get
// to that task until "expensive()" completes.
expensive();

Using setInterval()

The cousin of setTimeout() is the setInterval() function. As the name suggests, it accepts a callback function that's to be called at a regular interval. In fact, setInterval() takes the exact same arguments as setTimeout(). The only difference is that it will keep calling the function every x milliseconds until the timer is cleared using clearInterval().

This function is handy when we want to keep calling the same function, over and over. For example, if we poll an API endpoint, setInterval() is a good candidate solution. However, keep in mind that the scheduling of the callbacks is fixed. That is, once we call setInterval() with, say, 1000 milliseconds, there's no changing that 1000 milliseconds without first clearing the timer. For cases where the interval needs to be dynamic, using setTimeout() works better. The callback schedules the next interval, which allows the interval to be dynamic. For example, backing off from polling an API too frequently by increasing the interval.

In the setTimeout() example that we last looked at, we saw how running JavaScript code can mess with the event loop. That is, it prevents the event loop from consuming the task that invokes the JavaScript interpreter with our callback function. This allows us to defer code execution till some point in the future, but with no promises of accuracy. Let's see what happens when we schedule tasks using setInterval(). There's also some blocking JavaScript code that runs afterward:

// A counter for keeping track of which
// interval we're on.
var cnt = 0;

// Set up an interval timer. The callback will
// log which interval scheduled the callback.
var timer = setInterval(() => {
    console.log('Interval', ++cnt);
}, 3000);

// Block the CPU for a while. When we're no longer
// blocking the CPU, the first interval is called,
// as expected. Then the second, when expected. And
// so on. So while we block the callback tasks, we're
// also blocking tasks that schedule the next interval.
expensive(50000);
主站蜘蛛池模板: 烟台市| 泰来县| 长丰县| 黑山县| 桃江县| 荆门市| 祁连县| 柏乡县| 营口市| 常德市| 宣恩县| 桃源县| 七台河市| 巴林右旗| 正镶白旗| 通河县| 锦州市| 白河县| 陆丰市| 云安县| 贞丰县| 景德镇市| 巴中市| 长顺县| 临邑县| 湖北省| 郴州市| 信阳市| 平乡县| 卢氏县| 张家川| 新沂市| 深圳市| 岱山县| 石台县| 齐河县| 彭山县| 新和县| 雷山县| 桐梓县| 永寿县|