Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Block for stdin in Node.js

Short explanation:

I'm attempting to write a simple game in Node.js that needs to wait for user input every turn. How do I avoid callback hell (e.g. messy code) internal to a turn loop where each turn loop iteration needs to block and wait for input from stdin?

Long explanation:

All the explanations I have read on StackOverflow when someone asks about blocking for stdin input seem to be "that's not what Node.js is about!"

I understand that Node.js is designed to be non-blocking and I also understand why. However I feel that it has me stuck between a rock and a hard place on how to solve this. I feel like I have three options:

  1. Find a way to block for stdin and retain my while loop
  2. Ditch the while loop and instead recursively call a method (like nextTurn) whenever the previous turn ends.
  3. Ditch the while loop and instead use setTimeout(0, ...) or something similar to call a method (like nextTurn) whenever a turn ends.

With option (1) I am going against Node.js principles of non-blocking IO. With option (2) I will eventually reach a stack overflow as each call adds another turn to the call stack. With option (3) my code ends up being a mess to follow.

Internal to Node.js there are default functions that are marked **Sync (e.g. see the fs library or the sleep function) and I'm wondering why there is no Sync method for getting user input? And if I were to write something similar to fs.readSync how would I go about doing it and still follow best practices?

like image 510
JoshuaJ Avatar asked Apr 13 '14 15:04

JoshuaJ


People also ask

What is process Stdin in node JS?

The process. stdin property is an inbuilt application programming interface of the process module which listens for the user input. The stdin property of the process object is a Readable Stream. It uses on() function to listen for the event.

How do I close a Stdin node?

on('end', () => process. exit(exitCode));

How do I end a Stdin process?

Alternatively, after typing the input into shell, you can press Ctrl + D to send EOF (end-of-file) to trigger the event handler in process. stdin. on("end", ...)

How do I close a stdin stream?

I understand that since it inherits from stream, one should know that `pause` is how to "close" it, but for the sake of clarity for people who haven't had enough of their coffee or cocoa yet that day I would suggest adding: `process. stdin. pause` will "close" `stdin`.


2 Answers

Just found this: https://www.npmjs.com/package/readline-sync

Example code (after doing an npm install readline-sync)

var readlineSync = require('readline-sync');
while(true) {
  var yn = readlineSync.question("Do you like having tools that let you code how you want, rather than how their authors wanted?");
  if(yn === 'y') {
    console.log("Hooray!");
  } else {
    console.log("Back to callback world, I guess...");
    process.exit();
  }
}

Only problem so far is the wailing of the "That's not how node is meant to be used!" chorus, but I have earplugs :)

like image 197
ArtHare Avatar answered Oct 14 '22 15:10

ArtHare


I agree with the comment about moving towards an event based system and would ditch the loops. I've thrown together a quick example of text based processing which can be used for simple text games.

var fs = require('fs'),
    es = require('event-stream');

process.stdin
    .pipe(es.split())
    .on('data', parseCommand);

var actionHandlers = {};

function parseCommand(command) {
    var words = command.split(' '),
        action = '';

    if(words.length > 1) {
        action = words.shift();
    }

    if(actionHandlers[action]) {
        actionHandlers[action](words);
    } else {
        invalidAction(action);
    }
}

function invalidAction(action) {
    console.log('Unknown Action:', action);
}

actionHandlers['move'] = function(words) {
    console.log('You move', words);
}

actionHandlers['attack'] = function(words) {
    console.log('You attack', words);
}

You can now break up your actions into discrete functions which you can register with a central actionHandlers variable. This makes adding new commands almost trivial. If you can add some details on why the above approach wouldn't work well for you, let me know and I'll revise the answer.

like image 32
Timothy Strimple Avatar answered Oct 14 '22 17:10

Timothy Strimple