最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to create an interactive console interface using node js? - Stack Overflow

programmeradmin0浏览0评论

I am creating an console interface,in which the program ask some questions and user answers it through console,there are questions where the user must only enter limited no of inputs,i found some ways to get console input in node js but cant find a way to limit users inputs and ask questions one after another, after asking questions want to create a JSON object from it. for eg,i will ask questions like:

  • What is your name?
  • List 3 of your hobbies ?
  • Enter a desired username ?

After asking this questions i will construct a json object like

    {"name":"codebean","hobbies":['Exploring','Coding','Trucking'],"username":'codebean'}

Difficulties that i face are :

  • How to ask questions one after other, ie, serially

  • Limit the user inputs to a particular count

  • Terminate the program after entering the final question's answer

I only have little experience with NodeJs and what i was able to establish was nothing but rubbish,and here is what i established

process.stdin.setEncoding('utf8');
var input = [];
process.stdin.on('readable', function() {
  console.log('What is your name');
  var name = process.stdin.read();
  if ((name !== null) && (name !== '/n')) {
    input.push(name.split('/n')[0]);
    console.log(input);
  }
  console.log('List 3 of your hobbies ?');
  var hobbies = process.stdin.read();
});

process.stdin.on('end', function() {
  console.log(input);
});

I am creating an console interface,in which the program ask some questions and user answers it through console,there are questions where the user must only enter limited no of inputs,i found some ways to get console input in node js but cant find a way to limit users inputs and ask questions one after another, after asking questions want to create a JSON object from it. for eg,i will ask questions like:

  • What is your name?
  • List 3 of your hobbies ?
  • Enter a desired username ?

After asking this questions i will construct a json object like

    {"name":"codebean","hobbies":['Exploring','Coding','Trucking'],"username":'codebean'}

Difficulties that i face are :

  • How to ask questions one after other, ie, serially

  • Limit the user inputs to a particular count

  • Terminate the program after entering the final question's answer

I only have little experience with NodeJs and what i was able to establish was nothing but rubbish,and here is what i established

process.stdin.setEncoding('utf8');
var input = [];
process.stdin.on('readable', function() {
  console.log('What is your name');
  var name = process.stdin.read();
  if ((name !== null) && (name !== '/n')) {
    input.push(name.split('/n')[0]);
    console.log(input);
  }
  console.log('List 3 of your hobbies ?');
  var hobbies = process.stdin.read();
});

process.stdin.on('end', function() {
  console.log(input);
});

Share Improve this question edited Dec 27, 2015 at 16:51 Basilin Joe asked Dec 27, 2015 at 16:48 Basilin JoeBasilin Joe 7021 gold badge10 silver badges23 bronze badges 5
  • 1 Personally, having made CLIs and stuff in NodeJS, I would use a library for this sort of thing. Using the stdio streams is a bit messy when working with prompts. – user3117575 Commented Dec 27, 2015 at 16:51
  • 1 You can search npm for one you like, but a popular one like prompt might do. – user3117575 Commented Dec 27, 2015 at 16:53
  • Prompt seems nice but i am suppose to do it in the native way ? any help ?? – Basilin Joe Commented Dec 27, 2015 at 16:56
  • For a fancier text-based interface you could have a look at blessed. – Joost Vunderink Commented Dec 27, 2015 at 21:16
  • Blessed seems to be nice,thanks for the info – Basilin Joe Commented Dec 28, 2015 at 5:53
Add a comment  | 

3 Answers 3

Reset to default 14

Having made CLI applications in NodeJS, I would recommend using a library like prompt to better organize your code. A library would keep it more readable than what you can do naively (in my opinion).

However, if you want some native alternative, you could use Node's EventEmitter object to make things look a bit more organized, opposed to handling it all inside of the stdin callback:

var EventEmitter = require('events');
var prompt = new EventEmitter();
var current = null;
var result = {};
process.stdin.resume();

process.stdin.on('data', function(data){
  prompt.emit(current, data.toString().trim());
});

prompt.on(':new', function(name, question){
  current = name;
  console.log(question);
  process.stdout.write('> ');
});

prompt.on(':end', function(){
  console.log('\n', result);
  process.stdin.pause();
});

prompt.emit(':new', 'name', 'What is your name?');

prompt.on('name', function(data){
  result.name = data;
  prompt.emit(':new', 'hobbies', 'What are your hobbies?');
});

prompt.on('hobbies', function(data){
  result.hobbies = data.split(/,\s?/);
  prompt.emit(':new', 'username', 'What is your username?');
});

prompt.on('username', function(data){
  result.username = data;
  prompt.emit(':end');
});

This code uses some sort of a state-tracking method (I wouldn't know if there is an actual term).

Basically, there is a variable that keeps track of what you programming is looking for, in our case that is current. This variable is also used to trigger our prompt EventEmitter whenever data is received.

Inside events we can change the current variable to ask for something else (I made a shorthand :new event to do this), manipulate the data however we please, and also add it input our result variable.

If you want to "tag" your input (a little marker at the beginning), you can do that simply with stdin.write:

prompt.on(':new', function(){
  // ...
  process.stdin.write('> ');
});

Here is what that code looks like in action:

$ node ...
What is your name?
> Jamen Marzonie
What are your hobbies?
> programming, philosophy, petting cats
What is your username?
> jamen

 { name: 'Jamen Marzonie',
  hobbies: [ 'programming', 'philosophy', 'petting cats' ],
  username: 'jamen' }

I routinely use coroutines for this case, which yield questions and consume answers. Co-routines are generators which consume values to yield the next result. In our case they consume the user's responses, and yield prompts.

This makes for supremely readable and debuggable sequences of prompts and simple state management.

Note the use of function* in the example code which runs in node 14. This syntax defines a coroutine in javascript.

The first two 'library' functions in the example code at the end expose node's native stdio capabilities so that you can write 'stateful' coroutines such as...

function* createSimpleSequence(print) {
  print("Welcome to the game");
  let playerName = "";
  while(!playerName){
    playerName = yield "What is your name? ";
  }
  print(`Hello, ${playerName}`);
  yield "Press enter to restart the game";
}

Note that there is no explicit state management, as the co-routine allows us to 'pick up where we left off' after yielding a question, and we can use while loops and other apparently synchronous control flow primitives to decide how and when to 'progress' between states.

The full example requested by the original poster would look like...

function* createOriginalPostersSequence(print) {
  let name = "";
  while (!name) {
    name = yield "What is your name?";
    if (!name) {
      print("Your name cannot be empty");
    }
  }

  let hobbyString = "";
  while (!hobbyString) {
    hobbyString = yield "List three of your hobbies, separated by ','";
    const hobbyCount = hobbyString.split(",").length;
    if (hobbyCount !== 3) {
      if (hobbyCount === 0) {
        print("Your hobbies cannot be empty");
      } else if (hobbyCount == 1) {
        print("What! Do you really only have one hobby, think again!");
      } else if (hobbyCount == 2) {
        print("Two is better than one, but I asked for three!");
      }
      hobbyString = "";
    }
  }
  const hobbies = hobbyString.split(",").map((hobby) => hobby.trim());

  let username = "";
  while (!username) {
    username = yield "What is your username?";
    if (!username) {
      print("Your username cannot be empty!");
    }
    if (!username.match(/[a-z_]+/)) {
      print(
        "Your username can only contain lowercase letters and underscores."
      );
      username = "";
    }
  }
  const data = {
    name,
    hobbies,
    username,
  };
  print(`Your full data is ${JSON.stringify(data)}`);
}

Finally this is the full source code which would run both the simple sequence, and then the original poster's requested interactive prompt sequence interactively in Node 14.

// core 'library' exposing native node console capabilities for co-routines

function getAnswer() {
  process.stdin.resume();
  return new Promise((resolve) => {
    process.stdin.once("data", function (data) {
      resolve(data.toString().trim());
    });
  });
}

async function runSequence(sequenceFactory, clearScreen = true) {
  function print(msg, end = "\n") {
    process.stdout.write(msg + end);
  }
  let answer = undefined;
  const sequence = sequenceFactory(print);
  while (true) {
    const { value: question } = sequence.next(answer);
    if (question) {
      print(question, " : ");
      answer = await getAnswer();
      if (clearScreen) {
        console.clear();
      }
    } else {
      break;
    }
  }
}

// examples using the library

function* createSimpleSequence(print) {
  print("Welcome to the game");
  let playerName = "";
  while (!playerName) {
    playerName = yield "What is your name? ";
  }
  print(`Hello, ${playerName}`);
  yield "Press enter to restart the game";
}

function* createOriginalPostersSequence(print) {
  let name = "";
  while (!name) {
    name = yield "What is your name?";
    if (!name) {
      print("Your name cannot be empty");
    }
  }

  let hobbyString = "";
  while (!hobbyString) {
    hobbyString = yield "List three of your hobbies, separated by ','";
    const hobbyCount = hobbyString.split(",").length;
    if (hobbyCount !== 3) {
      if (hobbyCount === 0) {
        print("Your hobbies cannot be empty");
      } else if (hobbyCount == 1) {
        print("What! Do you really only have one hobby, think again!");
      } else if (hobbyCount == 2) {
        print("Two is better than one, but I asked for three!");
      }
      hobbyString = "";
    }
  }
  const hobbies = hobbyString.split(",").map((hobby) => hobby.trim());

  let username = "";
  while (!username) {
    username = yield "What is your username?";
    if (!username) {
      print("Your username cannot be empty!");
    }
    if (!username.match(/[a-z_]+/)) {
      print(
        "Your username can only contain lowercase letters and underscores."
      );
      username = "";
    }
  }
  const data = {
    name,
    hobbies,
    username,
  };
  print(`Your full data is ${JSON.stringify(data)}`);
}

// demo to run examples

async function run() {
  await runSequence(createSimpleSequence);
  await runSequence(createOriginalPostersSequence);
  process.exit(0);
}

run();

Here's an easy way to do it natively in Node 18.

const readline = require('node:readline/promises');  // Requires Node 18

async function main() {
    // Create interface
    const rl = readline.createInterface({input:process.stdin, output:process.stdout});

    // Ask questions
    const name = await rl.question("What is your name? ");
    rl.write("Enter three hobbies:\n");
    const hobby1 = await rl.question("  Hobby #1: ");
    const hobby2 = await rl.question("  Hobby #2: ");
    const hobby3 = await rl.question("  Hobby #3: ");
    const username = await rl.question("What username would you like? ");

    // Close interface
    rl.close();

    return {name, username, hobbies:[hobby1, hobby2, hobby3]};
}

main();
发布评论

评论列表(0)

  1. 暂无评论