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

时间:2015-12-27 16:48:49

标签: javascript json node.js

我正在创建一个控制台界面,其中程序提出一些问题,用户通过控制台回答它,有些问题用户必须只输入有限的输入,我找到了一些方法来获取控制台输入{{1但在找到想要从中创建node js对象的问题之后,无法找到一种限制用户输入并逐个问问题的方法。 例如,我会问类似的问题:

  • 你叫什么名字?
  • 列出你的爱好3?
  • 输入所需的用户名?

在提出这些问题后,我将构建一个JSON对象,如

json

我面临的困难是:

  • 如何一个接一个地提问,即连续提问

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

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

我对 {"name":"codebean","hobbies":['Exploring','Coding','Trucking'],"username":'codebean'} 只有很少的经验,而我能够建立的只不过是垃圾,这就是我所建立的

NodeJs

2 个答案:

答案 0 :(得分:10)

在NodeJS中创建了CLI应用程序后,我建议使用像prompt这样的库来更好地组织代码。一个图书馆可以让它比你天真可以做的更具可读性(在我看来)。

但是,如果你想要一些本地替代方案,你可以使用Node的EventEmitter object来使事情看起来更有条理,而不是在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。此变量还用于在收到数据时触发prompt EventEmitter。

在内部事件中,我们可以更改current变量以请求其他内容(我做了一个速记:new事件来执行此操作),然后操纵数据,我们也请输入我们的{ {1}}变量。

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

stdin.write

以下是该代码的实际效果:

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

答案 1 :(得分:0)

我经常在这种情况下使用协程,它会产生问题并消耗答案。协程是生成器,它消耗值来产生下一个结果。在我们的例子中,它们使用用户的响应,并产生提示。

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

请注意在节点 14 中运行的示例代码中使用了 function*。此语法在 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)}`);
}

最后,这是完整的源代码,它将在 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.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();