Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Node.js endless loop function, quit upon certain user input

I'm not quite grasping how exactly node works regarding async and loops. What I want to achieve here is have the console print out "Command: " and await for the user's input. But while it's waiting I want it to run "someRandomFunction()" endlessly until the user inputs "exit" onto the terminal.

Would appreciate all the help - and possibly an explanation so I can understand!

Thank you! :)

var rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

rl.question("Command: ", function(answer) {
    if (answer == "exit"){
        rl.close();
    } else {
        // If not "exit", How do I recall the function again?
    }
});

someRandomFunction();
like image 205
Bryan Avatar asked Jun 12 '14 10:06

Bryan


People also ask

How do you stop an infinite loop in node JS?

For NodeJS only – Use process. abort() or process. exit().

How do you stop a loop in node JS?

TL;DR: use break to exit a loop in JavaScript.

How do you stop a looping function?

break terminates the execution of a for or while loop. Statements in the loop after the break statement do not execute. In nested loops, break exits only from the loop in which it occurs. Control passes to the statement that follows the end of that loop.


2 Answers

I would suggest making the function repeatable like so.

var rl = readline.createInterface({
 input: process.stdin,
 output: process.stdout
});

var waitForUserInput = function() {
  rl.question("Command: ", function(answer) {
    if (answer == "exit"){
        rl.close();
    } else {
        waitForUserInput();
    }
  });
}

Then call

waitForUserInput();
someRandomFunction();

I'm unsure if the syntax you are using for .question is correct though, does that part of the code work?

You may also write this in the following manner.

var rl = readline.createInterface({
 input: process.stdin,
 output: process.stdout
});

function waitForUserInput() {
  rl.question("Command: ", function(answer) {
    if (answer == "exit"){
        rl.close();
    } else {
        waitForUserInput();
    }
  });
}

The important lesson here is that to re-use a function it has to be named and be available in scope. If you have any more questions about this please ask.

like image 186
Pablo Jomer Avatar answered Sep 24 '22 20:09

Pablo Jomer


The other answer is fine but uses recursion unnecessarily.

The key is to solving this is to separate, in your mind, the simple loop-based approach that is used in other languages from the asynchronous approach of Node.

In other languages, you might use a loop like this:

while not finished:
  line = readline.read() 
  if line == 'woof':
    print('BARK')
  elif line == 'exit':
    finished = True
  ... # etc

Node, at least with Readline, doesn't work this way.

In Node, you fire up Readline, give it event handlers, then return, and handle the completion of the readline loop later.

Consider this code, which you can copy-paste-run:

const readline = require('readline');

function replDemo() {
  return new Promise(function(resolve, reject) {
    let rl = readline.createInterface(process.stdin, process.stdout)
    rl.setPrompt('ready> ')
    rl.prompt();
    rl.on('line', function(line) {
      if (line === "exit" || line === "quit" || line == 'q') {
        rl.close()
        return // bail here, so rl.prompt() isn't called again
      }

      if (line === "help" || line === '?') {
        console.log(`commands:\n  woof\n  exit|quit\n`)
      } else if (line === "woof") {
        console.log('BARK!')
      } else if (line === "hello") {
        console.log('Hi there')
      } else {
        console.log(`unknown command: "${line}"`)
      }
      rl.prompt()

    }).on('close',function(){
      console.log('bye')
      resolve(42) // this is the final result of the function
    });
  })
}

async function run() {
  try {
    let replResult = await replDemo()
    console.log('repl result:', replResult)

  } catch(e) {
    console.log('failed:', e)
  }
}

run()

Run this and you'll get output like this:

$ node src/repl-demo.js
ready> hello
Hi there
ready> boo
unknown command: "boo"
ready> woof
BARK!
ready> exit
bye
repl result: 42

Note that the run function calls replDemo and "awaits" the result of the promise.

If you're unfamiliar with async/await, here's the same logic written it the "traditional" Promise style:

function run2() {
  replDemo().then(result => {
    console.log('repl result:', result)
  }).catch(e => {
    console.log('failed:', e)
  })
  console.log('replDemo has been called')
}

Note that I added output "replDemo has been called" for a reason - Running the above shows output like this:

$ node src/repl-demo.js
ready> replDemo has been called
woof
BARK!
ready> hello
Hi there
ready> bye
repl result: 42

Note how "replDemo has been called" appears immediately after the first "ready>" prompt. That's because the replDemo() function returns immediately, then run2() exits immediately, and the main is all done - yet the readline is still executing!

That's hard to grasp if you come from an imperative programming background like me. The async event-driven loop at the core of nodejs keeps running until all the work is done, and that occurs when the last promise is resolved, which happens when the readline instance is "closed", which happens when "exit" is entered by the user (or EOF is received, which is CTRL+D on most systems, and CTRL+Z on Windows).

like image 25
Andrew E Avatar answered Sep 24 '22 20:09

Andrew E