Why callback never gets called/executed inside while loop? [duplicate] - javascript

The following example is given in a Node.js book:
var open = false;
setTimeout(function() {
open = true
}, 1000)
while (!open) {
console.log('wait');
}
console.log('open sesame');
Explaining why the while loop blocks execution, the author says:
Node will never execute the timeout callback because the event loop is
stuck on this while loop started on line 7, never giving it a chance
to process the timeout event!
However, the author doesn't explain why this happens in the context of the event loop or what is really going on under the hood.
Can someone elaborate on this? Why does node get stuck? And how would one change the above code, whilst retaining the while control structure so that the event loop is not blocked and the code will behave as one might reasonably expect; wait
will be logged for only 1 second before the setTimeout fires and the process then exits after logging 'open sesame'.
Generic explanations such as the answers to this question about IO and event loops and callbacks do not really help me rationalise this. I'm hoping an answer which directly references the above code will help.

It's fairly simple really. Internally, node.js consists of this type of loop:
Get something from the event queue
Run whatever task is indicated and run it until it returns
When the above task is done, get the next item from the event queue
Run whatever task is indicated and run it until it returns
Rinse, lather, repeat - over and over
If at some point, there is nothing in the event queue, then go to sleep until something is placed in the event queue or until it's time for a timer to fire.
So, if a piece of Javascript is sitting in a while() loop, then that task is not finishing and per the above sequence, nothing new will be picked out of the event queue until that prior task is completely done. So, a very long or forever running while() loop just gums up the works. Because Javascript only runs one task at a time (single threaded for JS execution), if that one task is spinning in a while loop, then nothing else can ever execute.
Here's a simple example that might help explain it:
var done = false;
// set a timer for 1 second from now to set done to true
setTimeout(function() {
done = true;
}, 1000);
// spin wait for the done value to change
while (!done) { /* do nothing */}
console.log("finally, the done value changed!");
Some might logically think that the while loop will spin until the timer fires and then the timer will change the value of done to true and then the while loop will finish and the console.log() at the end will execute. That is NOT what will happen. This will actually be an infinite loop and the console.log() statement will never be executed.
The issue is that once you go into the spin wait in the while() loop, NO other Javascript can execute. So, the timer that wants to change the value of the done variable cannot execute. Thus, the while loop condition can never change and thus it is an infinite loop.
Here's what happens internally inside the JS engine:
done variable initialized to false
setTimeout() schedules a timer event for 1 second from now
The while loop starts spinning
1 second into the while loop spinning, the timer is ready to fire, but it won't be able to actually do anything until the interpreter gets back to the event loop
The while loop keeps spinning because the done variable never changes. Because it continues to spin, the JS engine never finishes this thread of execution and never gets to pull the next item from the event queue or run the pending timer.
node.js is an event driven environment. To solve this problem in a real world application, the done flag would get changed on some future event. So, rather than a spinning while loop, you would register an event handler for some relevant event in the future and do your work there. In the absolute worst case, you could set a recurring timer and "poll" to check the flag ever so often, but in nearly every single case, you can register an event handler for the actual event that will cause the done flag to change and do your work in that. Properly designed code that knows other code wants to know when something has changed may even offer its own event listener and its own notification events that one can register an interest in or even just a simple callback.

This is a great question but I found a fix!
var sleep = require('system-sleep')
var done = false
setTimeout(function() {
done = true
}, 1000)
while (!done) {
sleep(100)
console.log('sleeping')
}
console.log('finally, the done value changed!')
I think it works because system-sleep is not a spin wait.

There is another solution. You can get access to event loop almost every cycle.
let done = false;
setTimeout(() => {
done = true
}, 5);
const eventLoopQueue = () => {
return new Promise(resolve =>
setImmediate(() => {
console.log('event loop');
resolve();
})
);
}
const run = async () => {
while (!done) {
console.log('loop');
await eventLoopQueue();
}
}
run().then(() => console.log('Done'));

Node is a single serial task. There is no parallelism, and its concurrency is IO bound. Think of it like this: Everything is running on a single thread, when you make an IO call that is blocking/synchronous your process halts until the data is returned; however say we have a single thread that instead of waiting on IO(reading disk, grabbing a url, etc) your task continues on to the next task, and after that task is complete it checks that IO. This is basically what node does, its an "event-loop" its polling IO for completion(or progress) on a loop. So when a task does not complete(your loop) the event loop does not progress. To put it simply.

because timer needs to comeback and is waiting loop to finish to add to the queue, so although the timeout is in a separate thread, and may indeed finsihed the timer, but the "task" to set done = true is waiting on that infinite loop to finish

var open = false;
const EventEmitter = require("events");
const eventEmitter = new EventEmitter();
setTimeout(function () {
open = true;
eventEmitter.emit("open_var_changed");
}, 1000);
let wait_interval = setInterval(() => {
console.log("waiting");
}, 100);
eventEmitter.on("open_var_changed", () => {
clearInterval(wait_interval);
console.log("open var changed to ", open);
});
this exemple works and you can do setInterval and check if the open value changed inside it and it will work

Related

When does `setTimeout` execute? [duplicate]

I just started to learn Node.js and I need help: At the following code that runs on Node why it is stuck in a loop while it supposedly an asynchronous method? and also not waiting the interval.
class FuelBurner{
constructor(){
console.log("construction set");
this.fuel=0;
this.rate=2.0;
this.polluted=0.0;
}
run(){
while (true){
console.log("i run");
setTimeout( function (){this.polluted+=this.rate},1000);
this.fuel--;
}
}
get_pollution(){
return this.polluted;
}
}
console.log("Hello");
machine = new FuelBurner();
machine.run();
console.log("Why it never gets here?");
Asynchronous operations in JavaScript are put in a queue and only addressed by your code again when the main event loop isn't busy with something else.
You enter the while loop and pass a function to setTimeout.
Then the rest of the loop runs, and the loop starts again.
About a second later, the first timeout finishes and the function is put on the queue to run when the main event loop is free.
The main event loop is still running the while loop, and will do forever since the condition is always met. It never becomes free. It never pulls the function off the queue to run it.

Daemon setTimeout in js

I am writing a node.js library that requires setup in order to run. I have used setTimeout to show a warning if the setup function isn't run within 5 seconds.
However, this makes the program wait for 5 seconds before exiting, even if all the other code is finished.
I want to show a warning message iff the setup function hasn't been called and the program is still running after 5 seconds. Is there a way to do this with vanilla JS?
const done = new Event(); // Event is a simple class I made for a one-time event multicast
function setup() {
done.emit();
}
const timeoutHandle = setTimeout(() => showWarning(), 5000);
done.subscribe(() => {
clearTimeout(timeoutHandle);
});
console.log("The program should end here");
// The program keeps going for 5 seconds even after the last statement
However, this makes the program wait for 5 seconds before exiting, even if all the other code is finished.
You can utilize timeout.unref():
When called, the active Timeout object will not require the Node.js event loop to remain active. If there is no other activity keeping the event loop running, the process may exit before the Timeout object's callback is invoked. Calling timeout.unref() multiple times will have no effect.
So const timeoutHandle = setTimeout(() => showWarning(), 5000); timeoutHandle.unref();
This will prevent the setTimeout to keep the process active.

wait in while loop for external variable change or event?

I've a question/problem with an whileloop
I need to wait until something changes outside the while loop.
Let's say i have this while loop:
window.changeMe = true;
while(window.changeMe){
}
now i have these two options:
Change the changeMe variable via the Console/JavaScript Execution
Change the changeMe variable via an WebSocket Event
but neither is working, if i change the Variable directly, it is not changed.
If i trigger an WebSocket Event its not getting called.
Maybe its BLOCKED.. so is there any other way to change the variable?
I known i can use await and its already working that way, but the problem is that these functions with while are called via an Addon
and using many await's looks kinda ugly for the addon creator :(
an system with setTimeout & Callbacks are also working but also looks kinda ugly..
Yes, you are correct. Having a infinite while loop will prevent executing any other code from javascript event loop which occupies the main thread.
In order to imitate the same behavior you can implement your own while loop that is friendly to asynchronous events and external code execution. You have to use:
tail recursion in order to minimize the memory footprint,
setTimeout as a mechanism to allow other parts of your code to run asynchronously.
EXAMPLE:
window.changeMe = true;
let stop = setTimeout(() => { console.log("External change stop"); window.changeMe = false; }, 4000)
var whileLoop = () => {
console.log("Inside: ", window.changeMe)
return window.changeMe
? setTimeout(() => { whileLoop(); }, 0)
: false
}
whileLoop()
console.log("Outside: ", window.changeMe)
Here is a fiddle:
https://jsfiddle.net/qwmosfrd/
Here is a setInterval fiddle:
https://jsfiddle.net/2s6pa1jo/
Promise return value example fiddle:
https://jsfiddle.net/0qum6gnf/
JavaScript is single-threaded. If you have while (true) {}, then nothing else outside the while loop can change the state of your program. You need to change your approach. You probably want to set up event listeners instead or put this inside an async function so you can use await to release execution, or some other asynchronous API. But plain vanilla while () {} is synchronous and cannot be affected by other things while it is running.
You can't use a while loop in that way in nodejs.
Nodejs runs your Javascript in a single thread and the overall architecture of the environment is event driven. What your while loop is doing is a spin loop so while that loop is running, no other events can ever run. You have to return control back to the event loop before any other events can run. That means that timers, network events, etc... cannot run while your spin loop is running. So, in nodejs, this is never the right way to write code. It will not work.
The one exception could be if there was an await inside the loop which would pause the loop and allow other events to run.
So, while this is running:
while(window.changeMe){
}
No other events can run and thus nothing else gets a chance to change the changeMe property. Thus, this is just an infinite loop that can never complete and nothing else gets a chance to run.
Instead, you want to change your architecture to be event driven so that whatever changes the changeMe property emits some sort of event that other code can listen to so it will get notified when a change has occurred. This can be done by having the specific code that changes the property also notify listeners or it can be done by making the property be a setter method so that method can see that the property is being changed and can fire an event to notify any interested listeners that the value has changed.

Is node.js setTimeout() working?

I'm new to Node.js. Is there something I need to do to get setTimeout() to work?
Here's a code snippet.
async code that sets appMsg.doneLoadTables = true when done
do {
console.log('waiting ... ' + appMsg.doneLoadTables);
setTimeout(function() { console.log('waiting ...'); }, 1000);
} while (!appMsg.doneLoadTables);
Symptoms:
(While the two calls to console.log are similar, only the first prints the value of appMsg.doneLoadTables.) Every result includes that value.
The spacing between calls to console.log is much closer than 1000 msec. (I suspect the spacing is as fast as the computer can process the loop shown here.)
While I would hope the async routines could continue to process during the delays I intended here, I've never seen this loop finish; it's as if the loop takes all processing resources and prevents the async routines from finishing their work and from setting the variable that'll end this loop.
I had this experience with Node 4.2.1; I continue to have this experience after installing Node 5.0.0.
I've seen that similar questions about setTimeout() have been asked here many times before. I hope my use of a IIFE inside setTimeout() makes this question distinct from all of those.
Thanks in advance for any help offered ...
JavaScript is single-threaded. setTimeout is not a form of sleep which pauses code at that line. It works by "scheduling" your callback for later, and execute it when the stack exhausts (the engine doing nothing) and is at least n milliseconds later, where n is the delay you placed in milliseconds.
Now your code doesn't work because it never exits the loop. The code doesn't get the chance to execute other code (the code you hope to run and change appMsg.doneLoadTables's value). All it does keep logging "waiting... [something]".
Essentially you are polling. What you could use instead is setInterval. When appMsg.doneLoadTables is true, you stop the polling by using clearInterval.
I am not 100% sure what is your goal ... however maybe this snippet takes you where you want to go (I opted for setTimeout instead of setInterval):
var appMsg = {doneLoadTables: false};
var stillLoading = function() {
if(false === appMsg.doneLoadTables) {
console.log('waiting ... ' + appMsg.doneLoadTables);
setTimeout(stillLoading, 50);
}
else {
console.log('Loading complete.');
process.exit();
}
}
stillLoading();
setTimeout(function(){
console.log('Setting appMsg.doneLoadTables = true');
appMsg.doneLoadTables = true;
}, 1000);
The script polls status every 50ms and marks "done" exactly after 1 second.
The output looks like this
waiting ... false
waiting ... false
waiting ... false
waiting ... false
...
Setting appMsg.doneLoadTables = true
Loading complete.
(While the two calls to console.log are similar, only the first prints the value of appMsg.doneLoadTables.) Every result includes that value.
That is the correct behavior since you never exit the while loop. You stay in the same event frame that keeps looping forever.
The spacing between calls to console.log is much closer than 1000 msec. (I suspect the spacing is as fast as the computer can process the loop shown here.)
That is the correct behavior again because you callbacks that you passed to setTimeout will never execute unless you exit the do-while loop, which you never do. So you just keep calling first console.log statement then you add a callback to event loop to execute in 1000 ms without ever giving it (the callback that you pass) the chance to execute.
While I would hope the async routines could continue to process during the delays I intended here, I've never seen this loop finish; it's as if the loop takes all processing resources and prevents the async routines from finishing their work and from setting the variable that'll end this loop.
The loop never finish because it doesn't have logic implemented that finishes it. "Async routines" can't continue because that would require exiting the current event frame (that runs infinite loop) and starting the next one that has you callback that you passed to setTimeout.
Hope my explanations will help you to understand how asynchronous JavaScript works.

Why is 'nextTick' not behaving as expected here?

The following program will hang in NodeJS, does anybody know why?
ended = false;
events = require('events');
eventEmitter = new events.EventEmitter();
eventEmitter.on('end', function() {
ended = true;
});
setTimeout(function() {
eventEmitter.emit('end');
}, 100);
while (!ended) {
process.nextTick();
}
console.log('ended');
nextTick isn't some kind of yield operation, it's for scheduling a callback to be called the next time the engine is free to do so. It's "hanging" because the while loop's exit condition is never satisfied (and can never be, with that code).
Short answer: because Node.JS is single-threaded.
Long answer: JavaScript is organized into a queue which holds events. These events when fired cannot be stopped until they finish the job. Also no other code can run parallelly, because Node.JS is single threaded. What this means is that this code:
while (!ended) {
process.nextTick();
}
is an infinie loop. The ended variable will never change, because end handler cannot fire until the main event (i.e. the code you've shown us) finishes its job. And it never does.
process.nextTick(); does not call the next cycle of the main loop, the next cycle comes automatically, .nextTick() method is used to call a callback function on the next cycle, info: http://nodejs.org/api/process.html#process_process_nexttick_callback.
while(!ended) is an infinite loop and that's why the application hangs, the ended variable will not change until the current cycle does not end, which is flooded by your while loop.

Categories