Search code examples
node.jsterminalcallbackreadline

Why is readlineInterface.question() running early?


I have been working on a game called "Secret Message", wherein a player must guess a secret message hidden among several text files in a directory. My app is a node module so that I can use the Chalk5 ESM. I am using the readline callback API in order to evaluate user input.

I initialized a readline interface using process.stdin and process.stdout as my input and output respectively. My issue is that whenever I use the question() method, my code seems to run without my input. The same happens whenever I use the Node REPL; After I create a new interface using readline.createInterface, anything I text in the terminal gets doubled. For instance:

> const rl = require('readline')
// output: undefined
> const channel = rl.createInterface({input: process.stdin, output: process.stdout})
// output: undefined

Then, attempting to type channel.question( ... ) results in:

// This is my real input.
> cchaannnneell..qquueessttiioonn(())

Does anyone know what's going on?

Here is the relevant code section for the actual game:

function getDirParameter(dirParameter, directoryQuery) {
  let userChoice;
  channel.question(directoryQuery, (dirChoice) => {
    userChoice = dirChoice;
    console.log(dirChoice);
    switch(dirChoice) {
      case 'current':
        dirParameter = __dirname;
        break;
      case 'custom':
        console.log('User chose "custom".');
        break; 
      default: 
        console.error('Invalid option.');
        break;
    }
    channel.close();
  });
  
  return userChoice;
}

I have tried logging the user input before closing the interface. The terminal ends up logging nothing.


Solution

  • The code you show looks incomplete because you don't actually do anything different based on the switch() statement. But, here's a promisified version of that function:

    async function getDirParameter(channel, dirParameter, directoryQuery) {
        return new Promise((resolve, reject) => {
            channel.question(directoryQuery, (dirChoice) => {
                console.log(dirChoice);
                switch (dirChoice) {
                    case 'current':
                        dirParameter = __dirname;
                        break;
                    case 'custom':
                        console.log('User chose "custom".');
                        break;
                    default:
                        console.error('Invalid option.');
                        break;
                }
                resolve(dirChoice);
            });
        });
    }
    

    You would use this inside an async function like this:

    const dirChoice = await getDirParameter(channel, dirQuery);
    

    Note, this version of the function does not close the channel because it seems like the code that opens the channel should be the code that closes the channel and this function may want to be used more than once on the same channel. That's a design choice for you to decide on, but it made sense to me so I did the rewrite that way.

    P.S. Assigning to a function parameter like dirParameter and then not doing anything else with dirParameter doesn't actually accomplish anything so basically, you swtich() statement isn't accomplishing anything. Maybe there's more code there to come...

    Note: Starting with nodejs v17, there is an experimental interface for rl.question() that already returns a promise.