Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Waiting for user to enter input in Node.js

Tags:

node.js

I understand the rationale in Node.js for asynchronous events and I am learning how to write code that way. However, I am stuck with the following situation:

I want to write code that occasionally pauses for user input.

The program is not intended as a server (though currently it's intended for the command line). I realize this is an atypical use of Node. My goal is to eventually migrate the program back to a client-side Javascript application, but I find working in Node.js to be both fascinating and very useful for debugging. This brings me back to my example that illustrates the problem:

It reads in a text file and outputs each line unless the line ends with a "?". In that case, it should pause for user to clarify what was meant by that line. Currently my program outputs all lines first and waits for the clarifications at the end.

Is there any way to force node.js to pause for command-line input precisely for the cases where the conditional fires (i.e., where the line ends with a "?")?

var fs = require("fs");
var filename = "";
var i = 0;
var lines = [];

// modeled on http://st-on-it.blogspot.com/2011/05/how-to-read-user-input-with-nodejs.html
var query = function(text, callback) {
    process.stdin.resume();
    process.stdout.write("Please clarify what was meant by: " + text);
    process.stdin.once("data", function(data) {
        callback(data.toString().trim());
    });
};

if (process.argv.length > 2) {
    filename = process.argv[2];
    fs.readFile(filename, "ascii", function(err, data) {
        if (err) {
            console.error("" + err);
            process.exit(1);
        }
        lines = data.split("\n");
        for (i = 0; i < lines.length; i++) {
            if (/\?$/.test(lines[i])) { // ask user for clarification
                query(lines[i], function(response) {
                    console.log(response);
                    process.stdin.pause();
                });
            }
            else {
                console.log(lines[i]);
            }
        }
    });
}
else {
    console.error("File name must be supplied on command line.");
    process.exit(1);
}  
like image 396
blandish Avatar asked Aug 12 '13 18:08

blandish


4 Answers

Here's another way that has no dependencies (readline is built-in)

const readline = require('readline');  function askQuestion(query) {     const rl = readline.createInterface({         input: process.stdin,         output: process.stdout,     });      return new Promise(resolve => rl.question(query, ans => {         rl.close();         resolve(ans);     })) }   const ans = await askQuestion("Are you sure you want to deploy to PRODUCTION? "); 
like image 111
mpen Avatar answered Oct 15 '22 17:10

mpen


The trick is to not do it itteratively, but do the for loop recursively. So that the next line is printOut in a callback, that is called either A: after the line gets printed, or B: after the console input has been processed.

var fs = require("fs");  // modeled on http://st-on-it.blogspot.com/2011/05/how-to-read-user-input-with-nodejs.html function query(text, callback) {     'use strict';     process.stdin.resume();     process.stdout.write("Please clarify what was meant by: " + text);     process.stdin.once("data", function (data) {         callback(data.toString().trim());     }); }  function printLinesWaitForQuestions(lines, someCallbackFunction) {     'use strict';      function continueProcessing() {         if (lines.length) {             printNextLine(lines.pop());         } else {             someCallbackFunction();         }     }      function printNextLine(line) {          if (/\?$/.test(line)) { // ask user for clarification             query(line, function (response) {                 console.log(response);                 process.stdin.pause();                 continueProcessing();             });         } else {             console.log(line);             continueProcessing();         }     }      continueProcessing(); }  if (process.argv.length > 2) {     var filename = process.argv[2];     fs.readFile(filename, "ascii", function (err, data) {         'use strict';          if (err) {             console.error("" + err);             process.exit(1);         }          var lines = data.split("\n");         printLinesWaitForQuestions(lines, function () {             console.log('Were done now');         });     }); } else {     console.error("File name must be supplied on command line.");     process.exit(1); } 

This is a good solution for two reasons:

  1. It's relatively clean and the entire process can be contained within its own function closure, potentially leading to modularization.
  2. It doesn't break other asynchronous things you may want to do. There is no iterative waiting for loop and only one async task being launched per array of lines that you have. What if, in your version, you had millions of lines? You would have spun up millions of async outputs instantaneously... BAD! The recursive method not only allows better concurrency of other async work you want to do, but you don't clog the event loop with mini async tasks from one function call. This can cause memory issues, performance degradation, and other issues worthy of avoiding, particularly on large inputs.
like image 27
ChrisCM Avatar answered Oct 15 '22 17:10

ChrisCM


I found a module that does this really easily for yes or no:

https://www.npmjs.com/package/cli-interact

Install: npm install cli-interact --save-dev

How to use is taken straight from the npm site:

var query = require('cli-interact').getYesNo;
var answer = query('Is it true');
console.log('you answered:', answer);
like image 30
user3413723 Avatar answered Oct 15 '22 18:10

user3413723


Here's the same answer as mpen, but without the confusing / unnecessary promise wrapper:

const readline = require('readline');
const rl = readline.createInterface({input: process.stdin, output: process.stdout});

rl.question('Press [Y] to continue: ', ans => {
    if (ans == 'y') console.log('i will continue')
    else console.log('i will not continue');
    rl.close();
});
like image 42
stackers Avatar answered Oct 15 '22 16:10

stackers