Repeatedly prompt user until resolved using nodeJS async-await - javascript

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.

Related

Async/Await Not Waiting as I expect it

Please bear with me I've been dropped into a new project and trying to take it all in. I've been making progress over the last couple of day but can't seem to get over this last hump. Hopefully I can explain it correctly.
I'm loading up a web form and need to make a call out to the API to get some information that may or may not be present based on the data currently loading up. I've simplified my page to basically be this.
...Get some information the user wants and start to do some work to load
up the page and set up the form.
...old stuff working fine...
//Time for my new stuff
var testValue
async function test() {
await http.post(appConfig.serviceRootUrl + '/api/XXX/YYY',
{ mProperty: myObject.collectionInObject.itemInCollection }).then(function (result) {
if (result.length < 1) {
testValue= false;
}
else if (result[0].infoIWant.trim().length > 0) {
testValue= true;
}
});
}
test();
//Originally above in the if I was just seeing if I got a result
//and setting testValue to true/false but changed it for debugging
//and I am surely getting back correct values when the data exists
//or result.length zero when no data for it
...Do a bunch of more stuff that is old and working correctly....
//Test the new stuff up above
alert(testValue);
Most of the time I get back the correct true or false in the alert but once in a while I get back undefined. I'm guessing the undefined is because it is getting to the alert before the async/await finishes. I was under the impression it won't go past the line where I call "test();". I thought it was in effect making it halt anything below test(); until the await finished. Originally it was a bit more complex but I keep stripping it down to make it (hopefully) more basic/simple.
What am I missing in my thoughts or implementation?
Any help greatly appreciated as I'm chasing my tail at this point.
This isn't how async functions work. The function only appears to wait inside the function itself. Outside the function it is called and returns a promise synchronously.
In other words if you write:
let t = test()
t will be a promise that resolves when test() returns. In your current code, if you want to respond outside the function you would need something like:
async function test() {
let result = await http.post(appConfig.serviceRootUrl + '/api/XXX/YYY',
{ mProperty: myObject.collectionInObject.itemInCollection })
if (result.length < 1) return false
else if (result[0].infoIWant.trim().length > 0) return true;
}
// test is an async function. It returns a promise.
test().then(result => alert("result: " + result ))
Edit based on comments
Here's a working version using Axios for the http.post command:
async function test() {
let result = await axios.post('https://jsonplaceholder.typicode.com/posts',
{ mProperty: "some val" })
return result.data
}
// test is an async function. It returns a promise.
test().then(result => console.log(result ))
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
How about doing this?
...Get some information the user wants and start to do some work to load
up the page and set up the form.
...old stuff working fine...
//Time for my new stuff
var testValue
async function test() {
let promise = new Promise( (resolve, reject) => resolve( http.post(appConfig.serviceRootUrl + '/api/XXX/YYY',
{ mProperty: myObject.collectionInObject.itemInCollection }).then(function (result) {
if (result.length < 1) {
testValue= false;
}
else if (result[0].infoIWant.trim().length > 0) {
testValue= true;
}
})));
await promise;
alert(testValue);
}
test();
//Originally above in the if I was just seeing if I got a result
//and setting testValue to true/false but changed it for debugging
//and I am surely getting back correct values when the data exists
//or result.length zero when no data for it
...Do a bunch of more stuff that is old and working correctly....
//Test the new stuff up above
If you use .then() syntax, don't await, and vice versa. I was incorrect about browser compatibility with async/await, seems I haven't kept up with browser scripting, in favor of Node. But also, since you're using jQuery, $.ajax() might be a good option for you, because you don't need async/await or .then(), and you can do like so:
$.ajax(appConfig.serviceRootUrl + '/api/XXX/YYY', {
method: 'POST',
data: { mProperty: myObject.collectionInObject.itemInCollection }
}).done(function(data) {
//use result here just as you would normally within the `.then()`
})
I hope this is more helpful than my original answer.

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

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

This code doesn't seem to fire in order?

My problem is that the code does not seem to be running in order, as seen below.
This code is for my discord.js bot that I am creating.
var Discord = require("discord.js");
var bot = new Discord.Client();
var yt = require("C:/Users/username/Documents/Coding/Discord/youtubetest.js");
var youtubetest = new yt();
var fs = require('fs');
var youtubedl = require('youtube-dl');
var prefix = "!";
var vidid;
var commands = {
play: {
name: "!play ",
fnc: "Gets a Youtube video matching given tags.",
process: function(msg, query) {
youtubetest.respond(query, msg);
var vidid = youtubetest.vidid;
console.log(typeof(vidid) + " + " + vidid);
console.log("3");
}
}
};
bot.on('ready', () => {
console.log('I am ready!');
});
bot.on("message", msg => {
if(!msg.content.startsWith(prefix) || msg.author.bot || (msg.author.id === bot.user.id)) return;
var cmdraw = msg.content.split(" ")[0].substring(1).toLowerCase();
var query = msg.content.split("!")[1];
var cmd = commands[cmdraw];
if (cmd) {
var res = cmd.process(msg, query, bot);
if (res) {
msg.channel.sendMessage(res);
}
} else {
let msgs = [];
msgs.push(msg.content + " is not a valid command.");
msgs.push(" ");
msgs.push("Available commands:");
msgs.push(" ");
msg.channel.sendMessage(msgs);
msg.channel.sendMessage(commands.help.process(msg));
}
});
bot.on('error', e => { console.error(e); });
bot.login("mytoken");
The youtubetest.js file:
var youtube_node = require('youtube-node');
var ConfigFile = require("C:/Users/username/Documents/Coding/Discord/json_config.json");
var mybot = require("C:/Users/username/Documents/Coding/Discord/mybot.js");
function myyt () {
this.youtube = new youtube_node();
this.youtube.setKey(ConfigFile.youtube_api_key);
this.vidid = "";
}
myyt.prototype.respond = function(query, msg) {
this.youtube.search(query, 1, function(error, result) {
if (error) {
msg.channel.sendMessage("There was an error finding requested video.");
} else {
vidid = 'http://www.youtube.com/watch?v=' + result.items[0].id.videoId;
myyt.vidid = vidid;
console.log("1");
}
});
console.log("2");
};
module.exports = myyt;
As the code shows, i have an object for the commands that the bot will be able to process, and I have a function to run said commands when a message is received.
Throughout the code you can see that I have put three console.logs with 1, 2 and 3 showing in which order I expect the parts of the code to run. When the code is run and a query is found the output is this:
I am ready!
string +
2
3
1
This shows that the code is running in the wrong order that I expect it to.
All help is very highly appreciated :)
*Update! Thank you all very much to understand why it isn't working. I found a solution where in the main file at vidid = youtubetest.respond(query, msg) when it does that the variable is not assigned until the function is done so it goes onto the rest of my code without the variable. To fix I simply put an if statement checking if the variable if undefined and waiting until it is defined.*
Like is mentioned before, a lot of stuff in javascript runs in async, hence the callback handlers. The reason it runs in async, is to avoid the rest of your code being "blocked" by remote calls. To avoid ending up in callback hell, most of us Javascript developers are moving more and more over to Promises. So your code could then look more like this:
myyt.prototype.respond = function(query, msg) {
return new Promise(function(resolve, reject) {
this.youtube.search(query, 1, function(error, result) {
if (error) {
reject("There was an error finding requested video."); // passed down to the ".catch" statement below
} else {
vidid = 'http://www.youtube.com/watch?v=' + result.items[0].id.videoId;
myyt.vidid = vidid;
console.log("1");
resolve(2); // Resolve marks the promises as successfully completed, and passes along to the ".then" method
}
});
}).then(function(two) {
// video is now the same as myyt.vidid as above.
console.log(two);
}).catch(function(err) {
// err contains the error object from above
msg.channel.sendMessage(err);
})
};
This would naturally require a change in anything that uses this process, but creating your own prototypes seems.. odd.
This promise returns the vidid, so you'd then set vidid = youtubetest.response(query, msg);, and whenever that function gets called, you do:
vidid.then(function(id) {
// id is now the vidid.
});
Javascript runs async by design, and trying to hack your way around that leads you to dark places fast. As far as I can tell, you're also targetting nodeJS, which means that once you start running something synchronously, you'll kill off performance for other users, as everyone has to wait for that sync call to finish.
Some suggested reading:
http://callbackhell.com/
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
https://stackoverflow.com/a/11233849/3646975
I'd also suggest looking up ES6 syntax, as it shortens your code and makes life a hellofalot easier (native promises were only introduced in ES6, which NodeJS 4 and above supports (more or less))
In javascript, please remember that any callback function you pass to some other function is called asynchronously. I.e. the calls to callback function may not happen "in order". "In order" in this case means the order they appear on the source file.
The callback function is simply called on certain event:
When there is data to be processed
on error
in your case for example when the youtube search results are ready,
'ready' event is received or 'message' is received.
etc.

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