This code is WET (wrote everything twice) and hard to extend/reuse. What if you want to ask, say, 10 questions? How about 100? Following the original design would likely create a pyramid of doom.
We can DRY it out and flatten the pyramid with promises and a loop:
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const askQuestion = q => new Promise(res => rl.question(q, res));
(async () => {
for (;;) {
const answer = await askQuestion("What is the sum of 2+5: ");
if (answer === "7") {
console.log("You are correct");
rl.close();
break;
}
rl.write("You are wrong. Try again\n");
}
})();
Sample run:
What is the sum of 2+5: 6
You are wrong. Try again
What is the sum of 2+5: asdf
You are wrong. Try again
What is the sum of 2+5: 7
You are correct
Now it's easy to extend the code to support additional questions by moving the loop to a function askUntilCorrect
and adding a data structure to store questions and answers:
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const askQuestion = q => new Promise(res => rl.question(q, res));
const askUntilCorrect = async (question, answer) => {
for (;;) {
const response = await askQuestion(question);
if (response === answer) {
console.log("You are correct");
return;
}
rl.write("You are wrong. Try again\n");
}
};
(async () => {
const questions = [
{
question: "What is the sum of 2+5: ",
answer: "7",
},
{
question: "What is the result of 22-43: ",
answer: "-21",
},
{
question: "What is the largest city in the USA? ",
answer: "New York",
},
// ...
];
for (const {question, answer} of questions) {
await askUntilCorrect(question, answer);
}
rl.close();
})();
Sample run:
What is the sum of 2+5: 7
You are correct
What is the result of 22-43: -21
You are correct
What is the largest city in the USA? new york
You are wrong. Try again
What is the largest city in the USA? New York City
You are wrong. Try again
What is the largest city in the USA? NYC
You are wrong. Try again
What is the largest city in the USA? New York
You are correct
You can see there's still room for improvement -- it's poor user experience to basically get the correct answer but still be rejected by the program. The askUntilCorrect
function can accept a validator callback so the caller can specify something other than ===
as the test for correctness:
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const askQuestion = q => new Promise(res => rl.question(q, res));
const askUntilCorrect = async (
question,
answer,
validator = (r, a) => r === a
) => {
for (;;) {
const response = await askQuestion(question);
if (validator(response, answer)) {
console.log("You are correct");
return;
}
rl.write("You are wrong. Try again\n");
}
};
(async () => {
const questions = [
{
question: "What is the sum of 2+5: ",
answer: "7",
},
{
question: "What is the result of 22-43: ",
answer: "-21",
},
{
question: "What is the largest city in the USA? ",
validator: (r, a) =>
r.toLowerCase().includes("new york") ||
r.toLowerCase() === "nyc"
},
// ...
];
for (const {question, answer, validator} of questions) {
await askUntilCorrect(question, answer, validator);
}
rl.close();
})();
Sample run:
What is the sum of 2+5: 7
You are correct
What is the result of 22-43: -21
You are correct
What is the largest city in the USA? new brunswick
You are wrong. Try again
What is the largest city in the USA? new york
You are correct
There are still hardcoded strings in the askQuestion
function, so you can keep generalizing its behavior, but I'll call it good enough for now.
As an exercise, you might try extending this code to restrict the number of attempts for each question instead of prompting infinitely.
A few additional remarks on your code:
- Good use of
const
!
- (Minor) prefer 2 to 4 spaces indentation.
- Make sure lines never exceed ~70 characters.
- When you open a brace, generally don't put any more code on that line, as in
else { console.log('You are wrong. Try again');}
. Spread it over 3 lines.
- Your use of
==
as an intentional coercion is pretty clear, but on principle I never use ==
. It's just too hard to reason about, so you can't go wrong pretending it doesn't even exist. I prefer to explicitly convert the result with +
and use ===
, or use the string "7"
to represent the answer. This way, no potentially surprising coercion is involved and it's easier to follow intent. It might seem silly here, but if you always avoid ==
it'll make life easier in the long run.
rl.write()
seems more appropriate than rl.prompt()
for displaying the error message.
- Sometimes adding a
return
in an if
can save your else
from having to exist, and is typically cleaner for preconditions in a callback.