171

I thought to make an simple server http server with some console extension. I found the snippet to read from command line data.

  var i = rl.createInterface(process.stdin, process.stdout, null);
  i.question('Write your name: ', function(answer) {
    console.log('Nice to meet you> ' + answer);
    i.close();
    process.stdin.destroy();

  });

well to ask the questions repeatedly, i cant simply use the while(done) { } loop? Also well if the server receives output at the question time, it ruins the line.

jpaugh
  • 6,634
  • 4
  • 38
  • 90
Risto Novik
  • 8,199
  • 9
  • 50
  • 66
  • 6
    I assume by `rl` you mean [readline](https://stackoverflow.com/a/14513763/712526)? – jpaugh Dec 20 '17 at 18:53
  • You can use a non-blocking interface like the one used in [this answer](https://stackoverflow.com/a/54969612/1568394), then you can do a `while(done)` loop. – Keyvan Mar 03 '19 at 14:20

16 Answers16

194

you can't do a "while(done)" loop because that would require blocking on input, something node.js doesn't like to do.

Instead set up a callback to be called each time something is entered:

var stdin = process.openStdin();

stdin.addListener("data", function(d) {
    // note:  d is an object, and when converted to a string it will
    // end with a linefeed.  so we (rather crudely) account for that  
    // with toString() and then trim() 
    console.log("you entered: [" + 
        d.toString().trim() + "]");
  });
David Sherret
  • 101,669
  • 28
  • 188
  • 178
rob
  • 9,933
  • 7
  • 42
  • 73
  • 2
    Thank you this works, does the "end" listener allows to call some closing operations and say 'Goodbye' ? – Risto Novik Nov 15 '11 at 21:36
  • I removed the "end" listener from the example, I don't know where it will really be useful to be honest. – rob Nov 15 '11 at 23:08
  • how to control typed data with if statement ? or swtich case ? – Erdi Apr 23 '15 at 00:18
  • @OsmanErdi you just put your if or switch where the console.log() is. Do you need help doing a switch or an if? That's pretty basic stuff. – rob Apr 23 '15 at 04:12
  • 2
    You can simplify the string output to d.toString().trim() – Michael Mikhjian Aug 03 '15 at 19:54
  • 11
    This answer dates 2011 and much has changed since. In particular, the very first part of the answer, the *you can't do a while loop ...* doesn't hold anymore. Yes, you can have a while loop and still don't block, thanks to the the async-await pattern. Other answers reflect that. To anyone reading this nowadays - please consult other answers as well. – Wiktor Zychla Jan 16 '19 at 08:55
  • 1
    To follow on @WiktorZychla, the function process.openStdin while still working, was deprecated around 2011, and you will not find any documentation regarding it. – calder-ty Apr 28 '19 at 15:39
120

I've used another API for this purpose..

var readline = require('readline');
var rl = readline.createInterface(process.stdin, process.stdout);
rl.setPrompt('guess> ');
rl.prompt();
rl.on('line', function(line) {
    if (line === "right") rl.close();
    rl.prompt();
}).on('close',function(){
    process.exit(0);
});

This allows to prompt in loop until the answer is right. Also it gives nice little console.You can find the details @ http://nodejs.org/api/readline.html#readline_example_tiny_cli

Ben
  • 54,723
  • 49
  • 178
  • 224
Madhan Ganesh
  • 2,273
  • 2
  • 24
  • 19
  • 16
    This is a great answer. What might not be obvious (but is a big plus) is that readline is not external dependency: It's part of node.js. – jlh Jun 20 '17 at 08:01
  • I get Uncaught ReferenceError: "Whatever I wrote" is not defined. – brat Aug 22 '20 at 16:33
53

The Readline API has changed quite a bit since 12'. The doc's show a useful example to capture user input from a standard stream :

const readline = require('readline');

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

rl.question('What do you think of Node.js? ', (answer) => {
  console.log('Thank you for your valuable feedback:', answer);
  rl.close();
});

More information here.

Patrick.SE
  • 4,444
  • 5
  • 34
  • 44
  • 7
    this is just a basic example. How do you interact? question/answer? multiple choices and the like? How to re-open rl once closed, if can't how to work with open rl to interact with the user including some logic – Pawel Cioch Sep 28 '16 at 18:12
  • How will you store value of answer if we want to use in another function? – Infinity Aug 09 '21 at 09:22
  • @Infinity I think this may be a bit late but you can wrap the above code in a function that returns a promise that resolves to answer. With that, you can use async-await syntax to easily use the value of answer and store it or use it in other functions. – Yousef Essam Sep 24 '22 at 00:31
45

I believe this deserves a modern async-await answer, assuming node >= 7.x is used.

The answer still uses ReadLine::question but wraps it so that the while (done) {} is possible, which is something the OP asks about explicitely.

var cl = readln.createInterface( process.stdin, process.stdout );
var question = function(q) {
    return new Promise( (res, rej) => {
        cl.question( q, answer => {
            res(answer);
        })
    });
};

and then an example usage

(async function main() {
    var answer;
    while ( answer != 'yes' ) {
        answer = await question('Are you sure? ');
    }
    console.log( 'finally you are sure!');
})();

leads to following conversation

Are you sure? no
Are you sure? no
Are you sure? yes
finally you are sure!

Edit. In order to properly end the input, call

cl.close();

at the end of the script.

Wiktor Zychla
  • 47,367
  • 6
  • 74
  • 106
23

Please use readline-sync, this lets you working with synchronous console withouts callbacks hells. Even works with passwords:

var favFood = read.question('What is your favorite food? ', {
  hideEchoBack: true // The typed text on screen is hidden by `*` (default). 
});
Arango
  • 255
  • 3
  • 2
12

@rob answer will work most of the times, but it might not work as you expect with long inputs.

That is what you should be using instead:

const stdin = process.openStdin();
let content = '';

stdin.addListener('data', d => {
  content += d.toString();
});

stdin.addListener('end', () => {
  console.info(`Input: ${content}`);
});

Explanation on why this solution works:

addListener('data') works like a buffer, callback will be called when it is full or/and its the end of input.

What about long inputs? A single 'data' callback will not be enough, hence it you will get your input split in two or more parts. That is often not convenient.

addListener('end') will notify us when the stdin reader is done reading our input. Since we have been storing the previous data, we can now read and process it all together.

zurfyx
  • 31,043
  • 20
  • 111
  • 145
  • 3
    when i am using the code above and insert some input and then "enter" key the console keep asking me for more input. how shoud we terminate it? – Matan Tubul Oct 17 '18 at 08:33
9

Here's a example:

const stdin = process.openStdin()

process.stdout.write('Enter name: ')

stdin.addListener('data', text => {
  const name = text.toString().trim()
  console.log('Your name is: ' + name)

  stdin.pause() // stop reading
})

Output:

Enter name: bob
Your name is: bob
Miguel Mota
  • 20,135
  • 5
  • 45
  • 64
5

I recommend using Inquirer, since it provides a collection of common interactive command line user interfaces.

const inquirer = require('inquirer');

const questions = [{
  type: 'input',
  name: 'name',
  message: "What's your name?",
}];

const answers = await inquirer.prompt(questions);
console.log(answers);
Diogo Cardoso
  • 21,637
  • 26
  • 100
  • 138
3

This is overcomplicated. An easier version of:

var rl = require('readline');
rl.createInterface... etc

would be to use

var rl = require('readline-sync');

then it will wait when you use

rl.question('string');

then it is easier to repeat. for example:

var rl = require('readline-sync');
for(let i=0;i<10;i++) {
    var ans = rl.question('What\'s your favourite food?');
    console.log('I like '+ans+' too!');
}
2

A common use case would probably be for the app to display a generic prompt and handle it in a switch statement.

You could get a behaviour equivalent to a while loop by using a helper function that would call itself in the callback:

const readline = require('readline');
const rl = readline.createInterface(process.stdin, process.stdout);

function promptInput (prompt, handler)
{
    rl.question(prompt, input =>
    {
        if (handler(input) !== false)
        {
            promptInput(prompt, handler);
        }
        else
        {
            rl.close();
        }
    });
}

promptInput('app> ', input =>
{
    switch (input)
    {
        case 'my command':
            // handle this command
            break;
        case 'exit':
            console.log('Bye!');
            return false;
    }
});

You could pass an empty string instead of 'app> ' if your app already prints something to the screen outside of this loop.

zoran404
  • 1,682
  • 2
  • 20
  • 37
2

My approach to this would be to use async generators.

Assuming you have an array of questions:

 const questions = [
        "How are you today ?",
        "What are you working on ?",
        "What do you think of async generators ?",
    ]

In order to use the await keyword, you must wrap your program into an async IIFE.

(async () => {

    questions[Symbol.asyncIterator] = async function * () {
        const stdin = process.openStdin()

        for (const q of this) {
            // The promise won't be solved until you type something
            const res = await new Promise((resolve, reject) => {
                console.log(q)

                stdin.addListener('data', data => {
                    resolve(data.toString())
                    reject('err')
                });
            })

            yield [q, res];
        }

    };

    for await (const res of questions) {
        console.log(res)
    }

    process.exit(0)
})();

Expected results:

How are you today ?
good
[ 'How are you today ?', 'good\n' ]
What are you working on ?
:)
[ 'What are you working on ?', ':)\n' ]
What do you think about async generators ?
awesome
[ 'What do you think about async generators ?', 'awesome\n' ]

If you want to get questions an answers altogether, you can achieve this with a simple modification:

const questionsAndAnswers = [];

    for await (const res of questions) {
        // console.log(res)
        questionsAndAnswers.push(res)
    }

    console.log(questionsAndAnswers)

   /*
     [ [ 'How are you today ?', 'good\n' ],
     [ 'What are you working on ?', ':)\n' ],
     [ 'What do you think about async generators ?', 'awesome\n' ] ]
   */
Andrei Gătej
  • 11,116
  • 1
  • 14
  • 31
2

I had to write a "tic-tac-toe" game in Node that took input from the command line, and wrote this basic async/await block of code that did the trick.

const readline = require('readline')

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

async function getAnswer (prompt) {
  const answer = await new Promise((resolve, reject) =>{
    rl.question(`${prompt}\n`, (answer) => {
      resolve(answer)
    });
  })
  return answer
}

let done = false
const playGame = async () => {
  let i = 1
  let prompt = `Question #${i}, enter "q" to quit`
  while (!done) {
    i += 1
    const answer = await getAnswer(prompt)
    console.log(`${answer}`)
    prompt = processAnswer(answer, i)
  }
  rl.close()
}

const processAnswer = (answer, i) => {
  // this will be set depending on the answer
  let prompt = `Question #${i}, enter "q" to quit`
  // if answer === 'q', then quit
  if (answer === 'q') {
    console.log('User entered q to quit')
    done = true
    return
  }
  // parse answer

  // if answer is invalid, return new prompt to reenter

  // if answer is valid, process next move

  // create next prompt
  return prompt
}

playGame()
Stefan Musarra
  • 1,429
  • 14
  • 16
1

Blocking readline unblocked behaviour

Imagine you have three questions to be answered from the console, as you now know this code will not run because readline standard module has 'unblocked' behaviour say each rl.question is an independent thread so this code will not run.

'use strict';

var questionaire=[['First Question: ',''],['Second Question: ',''],['Third Question: ','']];

function askaquestion(question) {
const readline = require('readline');

const rl = readline.createInterface(
    {input: process.stdin, output:process.stdout}
    );
  rl.question(question[0], function(answer) {
    console.log(answer);
    question[1] = answer;
    rl.close();
  });
};

var i=0;  
for (i=0; i < questionaire.length; i++) {
askaquestion(questionaire[i]);
}

console.log('Results:',questionaire );

Running output:

node test.js
Third Question: Results: [ [ 'First Question: ', '' ],
  [ 'Second Question: ', '' ],
  [ 'Third Question: ', '' ] ]        <--- the last question remain unoverwritten and then the final line of the program is shown as the threads were running waiting for answers (see below)
aaa        <--- I responded with a single 'a' that was sweeped by 3 running threads
a        <--- Response of one thread

a        <--- Response of another thread

a        <--- Response of another thread (there is no order on threads exit)

The proposed solution uses an event emitter to signal the end of an unblocking thread, and includes the loop logic and end of program into its listener function.

'use strict';

var questionaire=[['First Question: ',''],['Second Question: ',''],['Third Question: ','']];

// Introduce EventEmitter object
const EventEmitter = require('events');

class MyEmitter extends EventEmitter {};

const myEmitter = new MyEmitter();
myEmitter.on('continue', () => {
  console.log('continue...');
  i++; if (i< questionaire.length) askaquestion(questionaire[i],myEmitter);    // add here relevant loop logic
           else console.log('end of loop!\nResults:',questionaire );
});
//

function askaquestion(p_question,p_my_Emitter) { // add a parameter to include my_Emitter
const readline = require('readline');

const rl = readline.createInterface(
    {input: process.stdin, output:process.stdout}
    );
  rl.question(p_question[0], function(answer) {
    console.log(answer);
    p_question[1] = answer;
    rl.close();
    myEmitter.emit('continue');    // Emit 'continue' event after the question was responded (detect end of unblocking thread)
  });
};

/*var i=0;  
for (i=0; i < questionaire.length; i++) {
askaquestion(questionaire[i],myEmitter);
}*/

var i=0;
askaquestion(questionaire[0],myEmitter);        // entry point to the blocking loop


// console.log('Results:',questionaire )    <- moved to the truly end of the program

Running output:

node test2.js
First Question: 1
1
continue...
Second Question: 2
2
continue...
Third Question: 3
3
continue...
done!
Results: [ [ 'First Question: ', '1' ],
  [ 'Second Question: ', '2' ],
  [ 'Third Question: ', '3' ] ]
vlc33
  • 91
  • 1
  • 2
1

Easiest way is to use readline-sync

It process one by one input and out put.

npm i readline-sync

eg:

var firstPrompt = readlineSync.question('Are you sure want to initialize new db? This will drop whole database and create new one, Enter: (yes/no) ');

if (firstPrompt === 'yes') {
    console.log('--firstPrompt--', firstPrompt)
    startProcess()
} else if (firstPrompt === 'no') {
    var secondPrompt = readlineSync.question('Do you want to modify migration?, Enter: (yes/no) ');
    console.log('secondPrompt ', secondPrompt)
    startAnother()
} else {
    console.log('Invalid Input')
    process.exit(0)
}
Rohit Parte
  • 3,365
  • 26
  • 26
0

I have craeted a little script for read directory and write a console name new file (example: 'name.txt' ) and text into file.

const readline = require('readline');
const fs = require('fs');

const pathFile = fs.readdirSync('.');

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

file.question('Insert name of your file? ', (f) => {
  console.log('File is: ',f.toString().trim());
  try{
    file.question('Insert text of your file? ', (d) => {
      console.log('Text is: ',d.toString().trim());
      try {
        if(f != ''){
          if (fs.existsSync(f)) {
            //file exists
            console.log('file exist');
            return file.close();
          }else{
            //save file
            fs.writeFile(f, d, (err) => {
                if (err) throw err;
                console.log('The file has been saved!');
                file.close();
            });
          }
        }else{
          //file empty 
          console.log('Not file is created!');
          console.log(pathFile);
          file.close();
        }
      } catch(err) {
        console.error(err);
        file.close();
      }
    });
  }catch(err){
    console.log(err);
    file.close();
  }
});
niksolaz
  • 102
  • 10
0
### Building on Rob's answer from 2011
a #CoffeeScript way to do the listener. ###

standin = process.stdin
out = process.stdout

standin.setEncoding('ascii')

# no data values returned inside the function
#  are available outside the function. 
#  So running entire program from listener.
standin.addListener('data', (buffer) ->
    
    arrayIn = buffer.toString().split('\n')
    t = lineIn()
    
    console.log("lineIn=" + lineIn() + " line #=" + lineNum 
        + " arrayIn=[" + arrayIn + "]")
)
Joseph Poirier
  • 386
  • 2
  • 17
  • Note: There was another question specifically asking for coffeescript, but it stated duplicate answer & appeared to table back to this question. – Joseph Poirier Nov 25 '22 at 16:50