21
while(1){
    rl.question("Command: ",function(answer){
    console.log(answer);
    })
}

Just tried this code, but instead get input one by one, it blink the "Command: " line. I know node.js is non-blocking but i don't know how to fix this problems.

user2477
  • 896
  • 2
  • 10
  • 23

3 Answers3

43
var readline = require('readline');
var log = console.log;

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

var recursiveAsyncReadLine = function () {
  rl.question('Command: ', function (answer) {
    if (answer == 'exit') //we need some base case, for recursion
      return rl.close(); //closing RL and returning from function.
    log('Got it! Your answer was: "', answer, '"');
    recursiveAsyncReadLine(); //Calling this function again to ask new question
  });
};

recursiveAsyncReadLine(); //we have to actually start our recursion somehow

The key is to not to use synchronous loops. We should ask next rl.question only after handling answer. Recursion is the way to go. We define function that asks the question and handles the answer and then call it from inside itself after answer handling. This way we starting all over, just like with regular loop. But loops don't care about ansyc code, while our implementation cares.

mynameisdaniil
  • 1,106
  • 8
  • 10
  • what happens when you call readline.question() again before the user respond to a previous call ? – Alex_Nabu Jun 08 '15 at 07:02
  • I'm no expert on node or js - but I imagine the "next" one is competing for the interface; so only one gets to write out at a time (and that same one gets the answer provided it's handled in the callback). When it completes, the next one is waiting. – OJFord Jul 09 '15 at 08:48
  • 10
    This works but this adds a new call stack every time a command is entered, thus possibly reaching a stackoverflow if too many commands are entered. Isn't there a constant-stack solution using rl.question() ? – Jérôme Beau Apr 05 '17 at 06:52
  • 5
    It doesn't add a call to the stack every time @Javarome: each execution of `recursiveAsyncReadLine()` prints the question, registers a callback and ends immediately. `recursiveAsyncReadLine()` is called by the callback function, not by itself. You can verify it by printing the stack on each invocation: `const stack = new Error().stack; console.log(stack);` – tato May 28 '19 at 21:08
32

Another option, via the Node.js documentation, is to use events:

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

rl.setPrompt('OHAI> ');
rl.prompt();

rl.on('line', function(line) {
    switch(line.trim()) {
        case 'hello':
            console.log('world!');
            break;
        default:
            console.log('Say what? I might have heard `' + line.trim() + '`');
        break;
    }
    rl.prompt();
}).on('close', function() {
    console.log('Have a great day!');
    process.exit(0);
});
nwayve
  • 2,291
  • 23
  • 33
  • Thanks, this works well for what I was trying to do. I have a tcp connection with event-based callbacks. Nice to be able to do this with events instead of loops. :-) – r0ber7 Dec 20 '15 at 22:45
  • For some reason I get the prompt back only when pressing `DEL`. – loretoparisi Mar 02 '19 at 22:20
6

Here's a version using async generator function:

async function* questions(query: string) {
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });

  try {
    for (;;) {
      yield new Promise((resolve) => rl.question(query, resolve));
    }
  } finally {
    rl.close();
  }
}

Which can be used with for await...of like this:

async function run() {
  for await (const answer of questions("Command: ")) {
    console.log(`I heard ${answer}`);
    if (answer == "done") break;
  }
}

run(); // For the sake of example, start the async function at the top level of nodejs script
Konstantin Spirin
  • 20,609
  • 15
  • 72
  • 90