Having an asynchronous issue with my JavaScript code when using a timeout with Node.js

54 Views Asked by At

So the purpose of the code is to import JSON questions and read them to the user in the terminal. The user gets 5 seconds to answer the question until the next question is displayed. The issue I am running into is that after the timer is triggered, the next question automatically times out even is the user enters an answer. Here is an example of my log:

Definition: A term used to describe a file or directory location on a drive
1. name
2. separator
3. path
4. prompt
 > 
Please answer within 5 seconds
Definition: A term used to describe a file or directory location on a drive
1. name
2. separator
3. path
4. prompt
 > 3
Please answer within 5 seconds
Definition: A term used to describe a file or directory location on a drive
1. name
2. separator
3. path
4. prompt
 > 3
Correct

Complete: Score - 2/2

And here is my function:

async function vocabularyDrillMode() {
    const definitions = loadJsonFile('definitions.json');
    let score = 0;
    const defBegin = definitions.length;

    async function drillTerm(currentDefinition) {
        console.log(`Definition: ${currentDefinition.definition}`);
        currentDefinition.possibleTerms.forEach((term, index) => console.log(`${index + 1}. ${term}`));

        return new Promise((resolve) => {
            const timer = setTimeout(() => {
                resolve({ success: false, term: "timeout" });
            }, 5000);

            rl.question(" > ", (userAnswer) => {
                clearTimeout(timer);

                const selectedAnswer = parseInt(userAnswer, 10) - 1;

                // Check if the user entered 'q'
                if (userAnswer.toLowerCase() === "q") {
                    resolve({ success: false, term: "q" });
                } else if (!isNaN(selectedAnswer) && selectedAnswer >= 0 && selectedAnswer <= 3) {
                    // If user entered a valid numeric answer
                    resolve({ success: selectedAnswer === currentDefinition.correctDefinition, term: selectedAnswer });
                } else {
                    // If user entered an invalid numeric answer or non-numeric input
                    resolve({ success: false, term: userAnswer });
                }
            });
        });
    }

    let quitDrill = false;

    while (definitions.length > 0 && !quitDrill) {
        const randomIndex = Math.floor(Math.random() * definitions.length);
        const currentDefinition = definitions[randomIndex];

        const result = await drillTerm(currentDefinition);

        if (result.success) {
            score++;
            definitions.splice(randomIndex, 1);
            console.log("Correct\n");
        } else if (result.term === "q") {
            quitDrill = true;
        } else if (result.term === "timeout") {
            console.log("Please answer within 5 seconds");
        } else if (isNaN(result.term) || result.term > 3 || result.term < 0) {
            console.log("Please enter a number 1-4 or 'q' to quit the program");
        } else {
            console.log("Incorrect.");
        }
    }

    const statusMessage = quitDrill ? `Incomplete: User quit the drill. Score: ${score}/${defBegin}` : `Complete: Score - ${score}/${defBegin}`; console.log(`${statusMessage}`);
}

I have tried pausing and resuming the readline and I have also tried reordering the console.log of the question and terms to inside the promise and neither solved the issue.

2

There are 2 best solutions below

0
Ricky Mo On

You should use an abort signal to cancel the question when timeout, otherwise you will be answering the previous question after timeout.

const ac = new AbortController();
const signal = ac.signal;
const timer = setTimeout(() => {
    ac.abort();
    resolve({ success: false, term: "timeout" });
}, 5000);

rl.question(" > ",{signal}, (userAnswer) => {
    //...
});
0
gre_gor On

The documentations shows how to timeout a question using the signal option.
There is no need to use setTimeout.

async function drillTerm(currentDefinition) {
    console.log(`Definition: ${currentDefinition.definition}`);
    currentDefinition.possibleTerms.forEach((term, index) => console.log(`${index + 1}. ${term}`));

    return new Promise((resolve) => {
        const signal = AbortSignal.timeout(5000);
        signal.addEventListener('abort', () => {
            resolve({ success: false, term: "timeout" });
        }, { once: true });

        rl.question(" > ", { signal },  (userAnswer) => {
            const selectedAnswer = parseInt(userAnswer, 10) - 1;

            // Check if the user entered 'q'
            if (userAnswer.toLowerCase() === "q") {
                resolve({ success: false, term: "q" });
            } else if (!isNaN(selectedAnswer) && selectedAnswer >= 0 && selectedAnswer <= 3) {
                // If user entered a valid numeric answer
                resolve({ success: selectedAnswer === currentDefinition.correctDefinition, term: selectedAnswer });
            } else {
                // If user entered an invalid numeric answer or non-numeric input
                resolve({ success: false, term: userAnswer });
            }
        });
    });
}