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

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();

Related

Javascript non-async function promise problem, how to fix it

I have 2 function: the first one is: readCSV that reads a csv file line by line, every lines contains a site name and for each one I have to call a web crawler.
the first function is:
async function readCSV(){
const fileStream = fs.createReadStream('./topm.csv');
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
for await (const line of rl) {
var currentline=line.split(",");
var current_site = "http://www."+currentline[1];
await crawling("http://www."+currentline[1])
}
}
and the other one is: crawling(web_page) that crawls the page.
async function crawling(web_page){
try{
new Crawler().configure({depth: 1})
.crawl(web_page, function onSuccess(page) {
console.log(page.url);
});
}catch(error){
console.log("Error: "+error.message);
}
}
every function is async, but when I call readCSV I noticed that the line with await crawling(.....) doesn't wait the end of each one and execute a lot of pages in parallel, giving me for some pages this waring:
(node:757) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 pipe listeners added to [Request]. Use emitter.setMaxListeners() to increase limit
I also noticed that with a file having 100 lines (100 web pages) the crawler is calling just 84 times... I don't understand why
At the end I tried to add a promise inside readCSV function, in this way:
async function readCSV(){
const fileStream = fs.createReadStream('./topm.csv');
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
for await (const line of rl) {
var currentline=line.split(",");
var current_site = "http://www."+currentline[1];
//await crawling("http://www."+currentline[1])
await (new Promise( resolve => {
new Crawler().configure({depth: 1})
.crawl(current_site, async (page) => {
console.log(page.url);
resolve();
});
}));
}
}
But in this way it works only for the first 4 site (to compile I need to add this --unhandled-rejections=strict)
From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
The await operator is used to wait for a Promise.
I think your problem is that your crawling function doesn't actually return a promise. So the code await crawling("http://www."+currentline[1]) evaluates immediately, returning the return value of the crawling function, which is undefined.
It is possible that this is the fix:
await new Crawler().configure({depth: 1})
//^^^
.crawl(web_page, function onSuccess(page) {
console.log(page.url);
});
It depends on what is returned by the crawl method, but if it returns a promise, that should be right.

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?

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.

How to read one line at a time and assign it to a variable in NodeJS?

Is there a simple way in node.js to do something like this, i.e. a function, e.g., readline reading exact one line from stdin and returning a string?
let a = parseInt(readline(stdin));
let b = parseFloat(readline(stdin));
I don't wish to read a whole block of lines and parse it line by line, like using process.stdin.on("data") or rl.on("line").
In the answer provided in http://stackoverflow.com/questions/20086849/how-to-read-from-stdin-line-by-line-in-node, every line is processed by the same function block, and I still can not assign each line to a variable on reading a line.
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
rl.on('line', function(line){
console.log(line);
})
It's pretty evident that reading a line from a stream is going to be an async operation anyway. (as you don't know when the line actually appears in the stream). So you should deal either with callbacks/promises or with generators. And when the read happens you get the line (in a callback, as a value returned in .then or simply by assign it to a variable as you want if you use generators).
So if it's an option for you to use ES6 and comething like 'co' to run the generator here is what you can try.
const co = require('co');
//allows to wait until EventEmitter such as a steam emits a particular event
const eventToPromise = require('event-to-promise');
//allows to actually read line by line
const byline = require('byline');
const Promise = require('bluebird');
class LineReader {
constructor (readableStream) {
let self = this;
//wrap to receive lines on .read()
this.lineStream = byline(readableStream);
this.lineStream.on('end', () => {
self.isEnded = true;
});
}
* getLine () {
let self = this;
if (self.isEnded) {
return;
}
//If we recieve null we have to wait until next 'readable' event
let line = self.lineStream.read();
if (line) {
return line;
}
yield eventToPromise(this.lineStream, 'readable');
//'readable' fired - proceed reading
return self.lineStream.read();
}
}
I used this to run it for test purposes.
co(function *() {
let reader = new LineReader(process.stdin);
for (let i = 0; i < 100; i++) {
//You might need to call .toString as it's a buffer.
let line = yield reader.getLine();
if (!line) {break;}
console.log(`Received the line from stdin: ${line}`);
}
});
Definitely it's going to work out of the box if you're using koa.js (generator based Express-like framework)
If you do not want ES6 you can do the same on bare Promises. It is going to be something like this. http://jsbin.com/qodedosige/1/edit?js

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

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).

Categories

Resources