Node.js endless loop function, quit upon certain user input - javascript

I'm not quite grasping how exactly node works regarding async and loops.
What I want to achieve here is have the console print out "Command: " and await for the user's input. But while it's waiting I want it to run "someRandomFunction()" endlessly until the user inputs "exit" onto the terminal.
Would appreciate all the help - and possibly an explanation so I can understand!
Thank you! :)
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question("Command: ", function(answer) {
if (answer == "exit"){
rl.close();
} else {
// If not "exit", How do I recall the function again?
}
});
someRandomFunction();

I would suggest making the function repeatable like so.
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
var waitForUserInput = function() {
rl.question("Command: ", function(answer) {
if (answer == "exit"){
rl.close();
} else {
waitForUserInput();
}
});
}
Then call
waitForUserInput();
someRandomFunction();
I'm unsure if the syntax you are using for .question is correct though, does that part of the code work?
You may also write this in the following manner.
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
function waitForUserInput() {
rl.question("Command: ", function(answer) {
if (answer == "exit"){
rl.close();
} else {
waitForUserInput();
}
});
}
The important lesson here is that to re-use a function it has to be named and be available in scope. If you have any more questions about this please ask.

The other answer is fine but uses recursion unnecessarily.
The key is to solving this is to separate, in your mind, the simple loop-based approach that is used in other languages from the asynchronous approach of Node.
In other languages, you might use a loop like this:
while not finished:
line = readline.read()
if line == 'woof':
print('BARK')
elif line == 'exit':
finished = True
... # etc
Node, at least with Readline, doesn't work this way.
In Node, you fire up Readline, give it event handlers, then return, and handle the completion of the readline loop later.
Consider this code, which you can copy-paste-run:
const readline = require('readline');
function replDemo() {
return new Promise(function(resolve, reject) {
let rl = readline.createInterface(process.stdin, process.stdout)
rl.setPrompt('ready> ')
rl.prompt();
rl.on('line', function(line) {
if (line === "exit" || line === "quit" || line == 'q') {
rl.close()
return // bail here, so rl.prompt() isn't called again
}
if (line === "help" || line === '?') {
console.log(`commands:\n woof\n exit|quit\n`)
} else if (line === "woof") {
console.log('BARK!')
} else if (line === "hello") {
console.log('Hi there')
} else {
console.log(`unknown command: "${line}"`)
}
rl.prompt()
}).on('close',function(){
console.log('bye')
resolve(42) // this is the final result of the function
});
})
}
async function run() {
try {
let replResult = await replDemo()
console.log('repl result:', replResult)
} catch(e) {
console.log('failed:', e)
}
}
run()
Run this and you'll get output like this:
$ node src/repl-demo.js
ready> hello
Hi there
ready> boo
unknown command: "boo"
ready> woof
BARK!
ready> exit
bye
repl result: 42
Note that the run function calls replDemo and "awaits" the result of the promise.
If you're unfamiliar with async/await, here's the same logic written it the "traditional" Promise style:
function run2() {
replDemo().then(result => {
console.log('repl result:', result)
}).catch(e => {
console.log('failed:', e)
})
console.log('replDemo has been called')
}
Note that I added output "replDemo has been called" for a reason - Running the above shows output like this:
$ node src/repl-demo.js
ready> replDemo has been called
woof
BARK!
ready> hello
Hi there
ready> bye
repl result: 42
Note how "replDemo has been called" appears immediately after the first "ready>" prompt. That's because the replDemo() function returns immediately, then run2() exits immediately, and the main is all done - yet the readline is still executing!
That's hard to grasp if you come from an imperative programming background like me. The async event-driven loop at the core of nodejs keeps running until all the work is done, and that occurs when the last promise is resolved, which happens when the readline instance is "closed", which happens when "exit" is entered by the user (or EOF is received, which is CTRL+D on most systems, and CTRL+Z on Windows).

Related

Javascript/Node/JSON question, why is this not working?

I thought I understood what I was doing until this wasn't going in order. I am running this through Node, not through a browser.
It goes to the prompt at the end of the while loop first. I don't know why.
const fs = require('fs');
const prompt = require('prompt-sync')();
function jsonReader(filepath, cb){
fs.readFile(filepath, 'utf-8', (err, fileData) => {
if (err) { return cb && cb(err); }
try {
const object = JSON.parse(fileData);
console.log(object);
return cb && cb(null, object);
} catch (err) {
return cs && cb(err);
}
});
}
var exit = 0;
do {
jsonReader('./customer.json', (err, customer) => {
if (err) {
console.log('Error reading file:',err)
return
}
//customer.order_count +=1
const note = prompt("Enter a note for the JSON file: ");
customer.note = note;
fs.writeFile('./customer.json', JSON.stringify(customer, null, 2), (err) =>{
if (err) {
console.log('Error writing file:',err)
} else {
console.log('File updated');
}
})
})
exit = prompt("Do you want to exit?");
} while (exit != 'y');
There are several things wrong with your code. The prompt problem you notice is just the beginning.
The way your code executes is like this:
// happens now
do {
// happens now
// ...
jsonReader('./customer.json', (err, customer) => {
// happens after readFile
fs.writeFile('./customer.json', JSON.stringify(customer, null, 2), (err) =>{
// happens after writeFile
// ...
})
// happens after readFile
// ...
})
// happens now
exit = prompt("Do you want to exit?");
} while (exit != 'y');
// happens now
The time sequence is as follows:
1. Things that happens now
2. Things that happens after readFile
3. Things that happens after writeFile
It is obvious that you are outputting the prompt before either readFile or writeFile.
However there is another problem. You have an infinite while loop. In node.js and in fact in the browser I/O only happens when there is no javascript to execute - in other words it happens when the interpreter is idle. You are preventing the script from reaching the end of script with the while loop therefore the interpreter is never idle.
For your script what will happen after you fix the prompt issue is:
1. Things that happens now
2. loop into things that happens now
3. loop into things that happens now
4. loop into things that happens now
5. loop into things that happens now
..
∞. loop into things that happens now
Thus the readFile and writeFile never executes. You need to replace the while loop either with a recursive asynchronous call or using setTimeout() or setInterval() or use a while loop with async/await.
Here's an implementation with miniminal changes to your code:
function doIt () { // <----------- replace the do..while loop
// ...
jsonReader('./customer.json', (err, customer) => {
// ...
fs.writeFile('./customer.json', JSON.stringify(customer, null, 2), (err) =>{
// ...
var exit = prompt("Do you want to exit?");
if (exit !== 'y') {
doIt(); // <-------------- repeat the process again
}
})
})
}
doIt(); // <--------- don't forget to begin the whole thing
This is admittedly not as easy to read as using async/await but I leave that implementation as homework. Besides, it requires much more changes to your existing code.
It is because fs.readFile is asynchronous, so your second prompt is executing before fs.readFile has finished. You may want to use async functions and await or put the prompt at the end of the callback for jsonReader.

Having issues with readline requesting input (possibly due to console.log)

I put together a script for grabbing user input via readline. The simplified version of the logic that's failing is as follows:
const rl = require('readline').createInterface({
input: process.stdin,
output: process.stdout
});
function ask() {
return new Promise((resolve, reject) => {
rl.question('Enter input:', (input) => resolve(input) );
});
}
async function logic() {
let result = await ask();
console.log(result); // this console.log seems fine
rl.close()
return result;
}
let a = logic();
console.log(a); // this console.log causes the issue
When I run the above, the output is:
Enter input: Promise { <pending> }
The culprit seems to be console.log() following the call to logic(). If I remove it, data is requested as expected and operation continues. But getting rid of all log messages in the code after readline won't work for this script. What can I do to fix this? Do I need to do something to process.stdout after closing input?

How to override previous rl.qu/estion with new one temporarily?

Note: I apologize for the "qu/estion" however stackoverflow blocked me from putting "question" in my title.
How do you temporarily override a previously asked rl.question with a new one that resolves and the old one can come back? Here is an example:
const readline = require('readline')
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
function log(message) {
readline.cursorTo(process.stdout, 0)
console.log(message)
rl.prompt(true)
}
function ask() {
rl.question('> ', async function(answer) {
log(answer)
if (answer === 'testfunc') {
await test()
}
ask()
})
}
async function test() {
await setTimeout(() => {
log('Attempting')
rl.question('can you see this? > ', function(answer) {
log('This answer: ' + answer)
})
}, 3000)
}
ask()
The log + ask() function allows for an in-process command line of sorts, letting you to always have the > at the bottom of the output and be able to always have access to commands.
I have a function, test(), that is async, and it wants to ask its own questions. I want the question within this async function to temporarily override the "permanent" one until an answer is given then the "permanent" one can have priority back.
Currently, the behavior is kind of interesting. At first glance, it seems like the second question has no effect, however if you type a few letters before the 3 seconds of wait time happens, you'll the see the cursor moved back to the beginning of the line, however it has no other real effect.
Edit: I do want the main rl.question available while the async function is running, except for when an rl.question is asked within the async function.
Edit 2: Expected behavior
1) you can write anything in for the main ask() question and it will just repeat.
2) If you write testfunc, it will go to the main ask() question, and allow you to type in it, until the 3 seconds on testfunc are done.
3) After this, testfunc's rl.question will trigger and override the main question.
4) Once testfunc's question is answered, the async function could continue if needed, and the main ask() question would still be available.
I believe you mention the core readline package of Node. It seems to take a callback so if you go the promise (async - await way) you need to define the event listener callback multiple times.
So first thing first you may start with promisifying the rl.question() function. One way of doing that could be.
var rlQuestion = q => new Promise(v => rl.question(q, a => (rl.close() , v(a))));
only then you may do like
var answer = await rlQuestion('What do you think of Node.js? ');
OK... Here we carry on with some further detail. The function following the await command should return a promise. So refractoring your your codebin code into something like below, i believe you should now be able to see better how it works.
const readline = require("readline"),
rl = readline.createInterface({ input : process.stdin,
output: process.stdout
}),
rlq = q => new Promise(v => rl.question(q, a => v(a))),
log = (...m) => {readline.cursorTo(process.stdout,0);
console.log(...m);
rl.prompt(true);};
async function test(){
return new Promise(v => setTimeout(async function(){
log("Attempting");
let answer = await rlq("Can you see this? >");
log("Answer: " + answer);
v(answer);
}, 3000));
}
async function ask(){
let answer = await rlq("> ");
answer = await (answer === "testfunc" ? test()
: Promise.resolve(answer));
log("ender ", answer);
/^q$|^quit$/i.test(answer) ? log("bye..!"): ask(); // the "test" function here is RegExp.test..!}
}
ask();
Edit as per your comment;
As I understand the async function (which is currently simulated to take 3 seconds) might last indefinitelly longer and during that period you want your prompt back right. Then obviously you are dealing with two separate asynchronous timelines (one for the normal console input and the second is the async process which may take an unknown amount of time) where both target the same entry point.
This is problematic since when test() function finalizes and invokes rlq (promisified rl.question) task the ask() function's rlq task is already at the head of the microtask queue (event loop of promises) and the test() function prompt won't appear. You have to get rid of the ask() question rlq task.
Enter rl.close(). So first we close the readline interface and then re-create it within the test function. By the way since we do reassignments to rl it can no longer be defined as const but var.
See if the following works for you.
var readline = require("readline"),
rl = readline.createInterface({ input : process.stdin,
output: process.stdout
}),
rlq = q => new Promise(v => rl.question(q, a => v(a))),
log = (...m) => {readline.cursorTo(process.stdout,0);
console.log(...m);
rl.prompt(true);},
test = _ => setTimeout(async function(){
log("Attempting");
rl.close();
rl = readline.createInterface({ input : process.stdin,
output: process.stdout
});
var answer = await rlq("Can you see this? >");
log(`Here i do something with ${answer} inside the test function`);
ask();
}, 3000);
async function ask(){
let answer = await rlq("> ");
answer === "testfunc" ? test()
: log(`Here i do something with ${answer}`);
/^q$|^quit$/i.test(answer) ? (log("bye..!"), rl.close())
: ask(); // the "test" function here is RegExp.test..!}
}
ask();

Repeatedly prompt user until resolved using nodeJS async-await

I try repeteadly asking question to user until they gave the correct answer using this code.
The problem is, it won't resolve if the user doesn't gave the right answer at the first time.
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
function promptAge() {
return new Promise(function(resolve){
rl.question('How old are you? ', function(answer) {
age = parseInt(answer);
if (age > 0) {
resolve(age);
} else {
promptAge();
}
});
});
}
(async function start() {
var userAge = await promptAge();
console.log('USER AGE: ' + userAge);
process.exit();
})();
Here are the terminal outputs for each condition:
When user gave the right answer the first time it was fine ...
How old are you? 14
USER AGE: 14
When user gave the wrong answer it was stuck (won't resolve and process won't exit) ...
How old are you? asd
How old are you? 12
_
When user doesn't gave any answer it was also stuck ...
How old are you?
How old are you?
How old are you? 12
_
Could anyone explain what happened or give me any reference to an article/video that explain this nature?
Btw, i try to do this using async/await for learning purpose (trying to learn how to do things asynchronously). I already tried this without async/await (promptAge() doesn't return any promise) and it was okay.
Thank you for your attention.
It's nothing to do with parseInt() although skellertor is advising good practice.
The problem is that you're generating a new Promise every time promptAge() is called - but the original caller i.e. start() only has visibility of the first Promise. If you enter a bad input, promptAge() generates a new Promise (inside a never-resolved Promise) and your success code will never run.
To fix this, only generate one Promise. There are more elegant ways to do this but for clarity, and to avoid hacking the code into something unrecognisable...
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
// the only changes are in promptAge()
// an internal function executes the asking, without generating a new Promise
function promptAge() {
return new Promise(function(resolve, reject) {
var ask = function() {
rl.question('How old are you? ', function(answer) {
age = parseInt(answer);
if (age > 0) {
// internal ask() function still has access to resolve() from parent scope
resolve(age, reject);
} else {
// calling ask again won't create a new Promise - only one is ever created and only resolves on success
ask();
}
});
};
ask();
});
}
(async function start() {
var userAge = await promptAge();
console.log('USER AGE: ' + userAge);
process.exit();
})();
It looks like it has to do with your parseInt() function. In both cases you are passing a non-Number value. First check if it is a number before parsing it into an int.

NodeJS readdirSync() not executed

I'm a beginner in non-blocking environment, such NodeJS. Below is my simple code, which list all files in directory :
var readline = require('readline');
var rl = readline.createInterface(process.stdin, process.stdout);
var fs = require('fs');
var datafolder = './datafolder';
var datafoldername = 'datafolder';
rl.setPrompt('Option> ');
rl.prompt();
rl.on('line', function(line) {
if (line === "right") rl.close();
if (line == '1') {
listFile();
}
rl.prompt();
}).on('close', function() {
process.exit(0);
});
function listFile() {
console.log(`File(s) on ${datafolder}`);
fs.readdirSync(datafolder, (err, files) => {
if (err) {
console.log(err);
} else {
files.forEach(filename => {
console.log(filename);
});
}
});
}
If user press 1, it's suppose to execute method listFile and show all files inside.
My question is, why fs.readdirSync not executed? The program works if I do it with readdir(), but it'll mess the output to user.
You are passing a callback to fs.readdirSync() but *Sync() functions don't take callbacks. The callback is never run (because the function does not take a callback), so you see no output. But fs.readdirSync() does in fact execute.
fs.readdirSync() simply returns it's value (which may make the program easier to read, but also means the call will block, which may be OK depending on what your program does and how it is used.)
var resultsArray = fs.readdirSync(datafolder);
(You may want to wrap it in a try/catch for error handling.)

Categories

Resources