Javascript 如何使用node js创建交互式控制台界面?

Javascript 如何使用node js创建交互式控制台界面?,javascript,json,node.js,Javascript,Json,Node.js,我正在创建一个控制台界面,在这个界面中,程序问一些问题,用户通过控制台回答,有些问题用户只能输入有限数量的输入,我在节点js中找到了一些获取控制台输入的方法,但找不到限制用户输入的方法,并且一个接一个地提问,提问后,您希望从中创建一个JSON对象。 例如,我将提出以下问题: 你叫什么名字 列出你的3个爱好 输入所需的用户名 在问了这些问题之后,我将构建一个类似于json的json对象 {"name":"codebean","hobbies":['Exploring','Coding'

我正在创建一个控制台界面,在这个界面中,程序问一些问题,用户通过控制台回答,有些问题用户只能输入有限数量的输入,我在
节点js中找到了一些获取控制台输入的方法,但找不到限制用户输入的方法,并且一个接一个地提问,提问后,您希望从中创建一个
JSON
对象。 例如,我将提出以下问题:

  • 你叫什么名字
  • 列出你的3个爱好
  • 输入所需的用户名
在问了这些问题之后,我将构建一个类似于json的
json
对象

    {"name":"codebean","hobbies":['Exploring','Coding','Trucking'],"username":'codebean'}
我面临的困难有:

  • 如何一个接一个地问问题

  • 将用户输入限制为特定计数

  • 输入最后一个问题的答案后终止程序

我对NodeJs只有很少的经验,我能建立的只是垃圾,以下是我建立的

process.stdin.setEncoding('utf8');
var输入=[];
process.stdin.on('readable',function(){
log(“你叫什么名字”);
var name=process.stdin.read();
如果((名称!==null)&&(名称!='/n')){
input.push(name.split('/n')[0]);
控制台日志(输入);
}
console.log('列出你的3个爱好?');
var=process.stdin.read();
});
process.stdin.on('end',function()){
控制台日志(输入);

});在NodeJS中创建了CLI应用程序之后,我建议使用类似库的方法来更好地组织代码。在我看来,一个图书馆会让它比你天真地做的事情更具可读性

但是,如果您想要一些本机的替代方案,您可以使用节点使事情看起来更有条理,而不是在stdin回调中处理它:

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');
});
这段代码使用某种状态跟踪方法(我不知道是否有实际的术语)

基本上,有一个变量跟踪您的编程正在寻找的内容,在我们的例子中是
current
。此变量还用于在收到数据时触发我们的
提示符
EventEmitter

在事件内部,我们可以更改
当前
变量以请求其他内容(我制作了一个速记
:new
事件来执行此操作),随意操作数据,还可以将其添加到
结果
变量中

如果您想“标记”您的输入(开头有一个小标记),只需使用
stdin即可。write

prompt.on(':new', function(){
  // ...
  process.stdin.write('> ');
});
下面是该代码在运行时的样子:

$node。。。
你的名字叫什么?
>杰门·马佐尼
你的爱好是什么?
>编程、哲学、抚摸猫
你的用户名是什么?
>杰门
{姓名:'Jamen Marzonie',
爱好:[“编程”、“哲学”、“抚摸猫”],
用户名:'jamen'}

我经常在这个案例中使用协同程序,它产生问题并使用答案。Co例程是使用值生成下一个结果的生成器。在我们的例子中,它们使用用户的响应,并产生提示

这使得提示序列和简单的状态管理具有极高的可读性和可调试性

注意在节点14中运行的示例代码中使用了
函数*
。该语法在javascript中定义了一个协同程序

示例代码末尾的前两个“库”函数公开了节点的本机stdio功能,这样您就可以编写“有状态”协程,例如

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";
}
请注意,没有明确的状态管理,因为协同例程允许我们在产生问题后“从哪里开始”,我们可以使用while循环和其他明显同步的控制流原语来决定如何以及何时在状态之间“前进”

原始海报要求的完整示例如下所示

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)}`);
}
最后,这是完整的源代码,它将运行简单序列,然后在节点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.stdin.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();

就个人而言,在NodeJS中创建了cli和其他东西之后,我会使用一个库来完成这类事情。使用stdio streams在使用提示时有点混乱。你可以搜索你喜欢的,但是像这样的流行提示可能会做。提示看起来不错,但我想用原生方式来做?有什么帮助吗?对于一个更华丽的基于文本的界面,你可以看看。祝福似乎很好,谢谢你的帮助info@CodeBean,希望这有帮助。请记住,单击绿色复选标记可以接受答案。如果你需要的话,请随时问我问题。谢谢你的时间,这真的很有帮助!