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();
promptmight do.