JAVA SCRIPT - Working with Node Timers and Understanding the Node Event Loop - Web Development and Design | Tutorial for Java, PHP, HTML, Javascript JAVA SCRIPT - Working with Node Timers and Understanding the Node Event Loop - Web Development and Design | Tutorial for Java, PHP, HTML, Javascript

Breaking

Post Top Ad

Post Top Ad

Thursday, March 14, 2019

JAVA SCRIPT - Working with Node Timers and Understanding the Node Event Loop

Working with Node Timers and Understanding the Node Event Loop 


Problem

You need to use a timer in a Node application, but you’re not sure which of Node’s three timers to use, or how accurate they are.

Solution

If your timer doesn’t have to be precise, you can use setTimeout() to create a single timer event, or setInterval() if you want a reoccurring timer:


setTimeout(function() {}, 3000);
setInterval(function() {}, 3000);


Both function timers can be canceled:


var timer1 = setTimeout(function() {}, 3000);
clearTimeout(timer1);
var timer2 = setInterval(function() {}, 3000);
clearInterval(timer2);


However, if you need more finite control of your timer, and immediate results, you might want to use setImmediate(). You don’t specify a delay for it, as you want the callback to be invoked immediately after all I/O callbacks are processed but before any setTimeout() or setInterval() callbacks:


setImmediate(function() {});


It, too, can be cleared, with clearImmediate().

EXPLAIN

Node, being JavaScript based, runs on a single thread. It is synchronous. However, input/ output (I/O) and other native API access either runs asynchronously or on a separate thread. Node’s approach to managing this timing disconnect is the event loop. In your code, when you perform an I/O operation, such as writing a chunk of text to a file, you specify a callback function to do any post-write activity. 

Once you’ve done so, the rest of your application code is processed. It doesn’t wait for the file write to finish. When the file write has finished, an event signaling the fact is returned to Node, and  pushed on to a queue, waiting for process. 

Node processes this event queue, and when it gets to the event signaled by the completed file write, it matches the event to the callback, and the callback is processed. As a comparison, think of going into a deli and ordering lunch. 

You wait in line to place your order, and are given an order number. You sit down and read the paper, or check your Twitter account while you wait. In the meantime, the lunch orders go into another queue for deli workers to process the orders. But each lunch request isn’t always finished in the order received. Some lunch orders may take longer. 

They may need to bake or grill for a longer time. So the deli worker processes your order by preparing your lunch item and then placing it in an oven, setting a timer for when it’s finished, and goes on to other tasks. When the timer pings, the deli worker quickly finishes his current task, and pulls your lunch order from the oven. 

You’re then notified that your lunch is ready for pickup by your order number being called out. If several time-consuming lunch items are being processed at the same time, the deli worker processes them as the timer for each item pings, in order. 

All Node processes fit the pattern of the deli order queue: first in, first to be sent to the deli (thread) workers. However, certain operations, such as I/O, are like those lunch orders that need extra time to bake in an oven or grill, but don’t require the deli worker to stop any other effort and wait for the baking and grilling. 

The oven or grill timers are equivalent to the messages that appear in the Node event loop, triggering a final action based on the requested operation. You now have a working blend of synchronous and asynchronous processes. But what happens with a timer? Both setTimeout() and setInterval() fire after the given delay, 

but what happens is a message to this effect is added to the event loop, to be processed in turn. So if the event loop is particularly cluttered, there is a delay before the the timer functions’ callbacks are called: 

It is important to note that your callback will probably not be called in exactly (delay) milliseconds. Node.js makes no guarantees about the exact timing of when the callback will fire, nor of the ordering things will fire in.

 The callback will be called as close as possible to the time specified. — Node Timers documentation For the most part, whatever delay happens is beyond the kin of our human senses, but it can result in animations that don’t seem to run smoothly. It can also add an odd effect to other applications.

 I created a scrolling timeline in SVG, with data fed to the client via WebSockets. To emulate real-world data, I used a three-second timer and randomly

generated a number to act as a data value. In the server code, I used setInterval(), because the timer is reoccurring:


var app = require('http').createServer(handler)
 , fs = require('fs');
var ws = require("nodejs-websocket");
app.listen(8124);
// serve static page
function handler (req, res) {
 fs.readFile(__dirname + '/drawline.html',
 function (err, data) {
 if (err) {
 res.writeHead(500);
 return res.end('Error loading drawline.html');
 }
 res.writeHead(200);
 res.end(data);
 });
}
// data timer
function startTimer() {
 setInterval(function() {
 var newval = Math.floor(Math.random() * 100) + 1;
 if (server.connections.length > 0) {
 console.log('sending ' + newval);
 var counter = {counter: newval};
 server.connections.forEach(function(conn, idx) {
 conn.sendText(JSON.stringify(counter), function() {
 console.log('conn sent')
 });
 });
 }
 },3000);
}
// websocket connection
var server = ws.createServer(function (conn) {
 console.log('connected');
 conn.on("close", function (code, reason) {
 console.log("Connection closed")
 });
}).listen(8001, function() {
 startTimer(); }
);


I included console.log() calls in the code so you can see that the timer event in com‐ parison to the communication responses.

When the setInterval() function is called, it’s pushed into the process. When its callback is processed, the WebSocket communi‐ cations are also pushed into the queue. The solution uses setInterval(), one of Node’s three different types of timers.


 The setInterval() function has the same format as the one we use in the browser. You specify a callback for the first function, provide a delay time (in milliseconds), and any potential arguments. The timer is going to fire in three seconds, but we already know that the callback for the timer may not be immediately processed. The same applies to the callbacks passed in the WebSocket sendText() calls.


These are based on Node’s Net (or TLS, if secure) sockets, and as the socket.write() (what’s used for sendText()) documentation notes: The optional callback parameter will be executed when the data is finally written out— this may not be immediately.


Node Net documentation If you set the timer to invoke immediately (giving zero as the delay value), you’ll see that the data sent message is interspersed with the communication sent message (before the browser client freezes up, overwhelmed by the socket communications—you don’t want to use a zero value in the application again).


However, the timelines for all the clients remain the same because the communications are sent within the timer’s callback function, synchronously, so the data is the same for all of the communications—it’s just the callbacks that are handled, seemingly out of order. Earlier I mentioned using setInterval() with a delay of zero.


In actuality, it isn’t exactly zero—Node follows the HTML5 specification that browsers adhere to, and “clamps” the timer interval to a minimum value of four milliseconds. While this may seem to be too small of an amount to cause a problem, when it comes to animations and time-critical processes the time delay can impact the overall appearance and/or function. To bypass the constraints, Node developers utilized Node’s process.nextTick() in‐ stead.


The callback associated with process.nextTick() is processed on the next event loop go around, usually before any I/O callbacks (though there are constraints, which I’ll get to in a minute). No more pesky four millisecond throttling. But then, what hap‐ pens if there’s an enormous number of recursively called process.nextTick() calls? To return to our deli analogy, during a busy lunch hour, workers can be overrun with orders and so caught up in trying to process new orders that they don’t respond in a timely manner to the oven and grill pings.


Things burn when this happens. If you’ve ever been to a well-run deli, you’ll notice the counter person taking the orders will assess the kitchen before taking the order, tossing in some slight delay, or even taking on some of the kitchen duties, letting the people wait just a tiny bit longer in the order queue.

The same happens with Node. If process.nextTick() were allowed to be the spoiled child, always getting its way, I/O operations would get starved out. Node uses another value, process.maxTickDepth, with a default value of 1000 to constrain the number of process.next() callbacks that are processed before the I/O callbacks are allowed to play.


It’s the counter person in the deli. In more recent releases of Node, the setImmediate() function was added. This function attempts to resolve all of the issues associated with the timing operations and create a happy medium that should work for most folks. When setImmediate() is called, its callback is added after the I/O callbacks, but before the setTimeout() and setInterv al() callbacks.


We don’t have the four millisecond tax for the traditional timers, but we also don’t have the brat that is process.nextTick(). To return one last time to the deli analogy, setImmediate() is a customer in the order queue who sees that the deli workers are overwhelmed with pinging ovens, and politely states he’ll wait to give his order. 

No comments:

Post a Comment

Post Top Ad