In the following code:
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
sleep(10000);
Why NodeJS doesn't exit immediately? What causes the Promise to be waited for?
I thought I needed await sleep(10000) - but this gives an error.
Nodejs waits for timers that are still active before exiting. You can tell it to NOT wait for a timer by calling .unref() on the timer object itself.
So, it's not actually the promise it is waiting for, but rather the timer it is waiting for.
Internally, node.js keeps a reference count of the number of open timers that have not been .unref() and will not exit until that count (among other things) gets to zero.
Here's a couple excerpts from the node.js doc for timers:
Class: Timeout
By default, when a timer is scheduled using either setTimeout() or setInterval(), the Node.js event loop will continue running as long as the timer is active. Each of the Timeout objects returned by these functions export both timeout.ref() and timeout.unref() functions that can be used to control this default behavior.
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.
Take a look at the unref() function for timers in node - https://nodejs.org/api/timers.html#timers_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.
You can create a timeout and call the unref() function on it - this will prevent node from staying alive if the only thing it is waiting for is the timeout.
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms).unref();
});
}
As a side note, the same unref function can also be used for setTimeout calls.
As correctly noted by jfriend00, it is not the promise that is keeping node alive, it is the timeout.
Related
I'm new to JavaScript and NodeJS so forgive me if this question is rudimentary.
Let's say I just have a simple file hello.js and I run it with $ node hello.js and all this file contains is
setTimeout(() => {console.log('hello');}, 5000);
Why doesn't this program finish immediately? Why instead does it wait for the underlying Promise to resolve?
After all, isn't the Promise associated with setTimeout created and run asynchronously? So wouldn't the main 'thread' of execution "fall off" when it encounters no more code to run?
The Node event loop keeps running until all outstanding tasks are completed or cancelled.
setTimeout creates a pending event, so the loop will keep running until that executes.
Outstanding Promises, setInterval and other mechanisms can all prevent the event loop from halting.
It's worth noting that setTimeout does not use a promise at all. That's just a regular callback function. The setTimeout() API long predates Promises.
I think there's more to the story/explanation here so I'll add an answer that contains additional info.
Nodejs keeps a reference counter for all unfinished asynchronous operations and nodejs itself will not exit automatically until all the asynchronous operations are complete (until the reference count gets to zero). If you want nodejs to exit before that, you can call process.exit() whenever you want.
Since setTimeout() is an asynchronous operation, it contributes to the reference count of unfinished asynchronous operations and thus it keeps nodejs from automatically exiting until the timer fires.
Note that setTimeout() does not use a promise - it's just a plain callback. It is not promises that nodejs waits for. It is the underlying asynchronous operations that promises often are attached to that it actually waits for.
So, if you did just this:
const myPromise = new Promise((resolve, reject) => {
console.log("did nothing here");
});
Then, nodejs would not wait for that promise all by itself, even though that promise never resolves or rejects. This is because nodejs does not actually wait for promises. It waits for the underlying asynchronous operations that are usually behind promises. Since there is no such underlying asynchronous operation behind this promise, nodejs does not wait for it.
It's also worth mentioning that you can actually tell nodejs to NOT wait for some asynchronous operations by using the .unref() method. In the example you show, if you do this:
const timer = setTimeout(() => {console.log('hello');}, 5000);
timer.unref();
Then, nodejs will NOT wait for that timer before exiting and if this is your entire program, nodejs will actually exit immediately without waiting for the timer to fire.
As an example, I have a nodejs program that carries out some nightly maintenance using a recurring timer. I don't want that maintenance timer to keep the nodejs program running if the other things that it's doing are done. So, after I set the timer, I call the .unref() method on it.
I have a Node application creating a number of worker processes via fork() to perform some tasks on their own. As a byproduct, the workers produce a snapshot of actions taken in the form of object. Each worker has an event listener attached to the 'exit' event, at which time they send their snapshot back to the parent process via process.send().
Here's an example of the set up:
// parent.js
const exec = require('child_process');
const worker = exec.fork('worker.js', [], {
stdio: ['pipe', 'pipe', null, 'ipc']
});
worker.on('message', (snapshot) => {
// Handle the snapshot sent from worker
});
// worker.js
process.on('exit', () => {
process.send({ /* snapshot data */ })
});
/* Arbitrary task work */
Is this an acceptable pattern for parent/worker IPC? Specifically, will this reliably about the reliability of receiving messages in the parent process given the nature of the exit event and process.send()?
Is the beforeExit event better suited to this pattern?
It is pretty clear in the nodejs doc that you should only do synchronous operations in the exit event. Here's an excerpt from the doc for the exit event:
Listener functions must only perform synchronous operations. The Node.js process will exit immediately after calling the 'exit' event listeners causing any additional work still queued in the event loop to be abandoned.
So, doing an async operation in the exit event is likely a crapshoot that will depend upon internal implementation and whether it needs the event loop to work.
You can do asynchronous things in the beforeExit event so that is where you should probably do it. Excerpt from beforeExit doc:
The 'beforeExit' event is emitted when Node.js empties its event loop and has no additional work to schedule. Normally, the Node.js process will exit when there is no work scheduled, but a listener registered on the 'beforeExit' event can make asynchronous calls, and thereby cause the Node.js process to continue.
The listener callback function is invoked with the value of process.exitCode passed as the only argument.
The 'beforeExit' event is not emitted for conditions causing explicit termination, such as calling process.exit() or uncaught exceptions.
The 'beforeExit' should not be used as an alternative to the 'exit' event unless the intention is to schedule additional work.
I have the following code...
async function finish(){
console.log("Finishing");
console.time("fin");
let test = await new Promise(function(res){
setTimeout(()=>{res(test)}, 2000);
});
console.timeEnd("fin");
console.log(test);
};
process.on('exit', finish);
I would expect this to wait two second on exit and print out a timestamp close to 2s. However, when I run the timestamp is shorter and doesn't print any line after Finishing.
How do I wait for a timeout on exit?
From the node docs, you cannot use asynchronous code in the exit event.
Listener functions must only perform synchronous operations. The Node.js process will exit immediately after calling the 'exit' event listeners causing any additional work still queued in the event loop to be abandoned.
If you want to schedule additional work before exiting (e.g. your asynchronous function), you need to use beforeExit.
process.on('beforeExit', finish);
Having said that, you'll also need to recognize that beforeExit is only emitted when the process is out of work to do, so a) it'll not emit if something explicitly calls for termination (e.g. process.exit()) and b) it'll keep emitting unless that happens.
Nodejs has a method process.nextTick(fn) which delays the execution of a function until the next tick. According to this article this can be used to emit events in the constructor of an object like this (copied from the artice):
function StreamLibrary(resourceName) {
var self = this;
process.nextTick(function() {
self.emit('start');
});
}
var stream = new StreamLibrary('fooResource');
stream.on('start', function() {
console.log('Reading has started');
});
Without process.nextTick this wouldn't have worked because the event would've been emitted before you had the chance to listen for events. However, how can you be certain that the event won't be emitted before you added an event listener? If it's fully asynchronous then there would be a chance that the next tick is executed before the end of the constructor, hence ignoring the event listener. So what are the rules behind this process? When does node executes a nextTick?
The next tick is executed on... the next tick. I think the confusion here is what you mean by "fully asynchronous".
With Node.js, all of your code runs on a single thread. No code scheduled for the next tick will be executed until the current tick is finished.
Therefore, when you emit events from the next tick within the constructor, the code that may attach handlers will all be finished executing before that next tick occurs.
The asynchronous and multi-threaded part of Node is with handling operations such as network and IO. Those function calls run while your JavaScript code is running, but when data is passed back to your code, it won't run until that next tick.
I want to trigger the setTimeout callback function, but it seems not work. What's the problem?
var fs = require("fs");
// set timeout callback
setTimeout(function(){
console.log("5000ms timeout");
process.exit(0);
}, 5000 );
// do something more than 5000ms
while(true) {
var stats = fs.statSync("foo");
console.log("while statement running...");
}
when I run this, after 5s, the program is still running
The while(true) is a tight spin loop which prevents any other asynchronous callbacks from firing. Don't do this in a single-threaded environment. You can use setInterval with a small timeout instead of while(true).
Javascript is strictly single-threaded. (except for workers)
As long as your while loop is running, no other Javascript code can execute at all, including your setTimeout callback.
By contrast, calling setInterval simply schedules a callback to run periodically, but doesn't block the thread in the interim.
I'm not familiar with node.js but I would normally expect the while loop to keep running. JS is blocking. In order to stop that loop, it's condition needs to evaluate as false. Until the loop stops, nothing else will execute.