1

I know readline uses Readable stream. But I am surprised to find that when I close readline, readline.question still prompts the question, but the callback won't be called and the process hangs!

const readline = require('readline').createInterface({
    input: process.stdin,
    output: process.stdout
  })
  
  readline.question(`What's your name?`, name => {
    console.log(`Hi ${name}!`)
    readline.close()
    readline.question(`How are you?`, how => {
      console.log(`Good to know you are ${how}!`)
      //readline.close() should close here
    })
  })

https://nodejs.org/api/readline.html#rlclose says "Calling rl.close() does not immediately stop other events"

The rl.close() method closes the InterfaceConstructor instance and relinquishes control over the input and output streams. When called, the 'close' event will be emitted.

Calling rl.close() does not immediately stop other events (including 'line') from being emitted by the InterfaceConstructor instance.

But even if I setTimeout to call readline.question later, readline.question still prompts the question, and the callback still won't be called and the process hangs.

Why does readline act like this? I feel this is a bug and I raised an issue against nodejs.

--- update ---

I raised the issue to nodejs here https://github.com/nodejs/node/issues/42450 and they fixed it at nodejs18, check here https://github.com/nodejs/node/pull/42262 ((SEMVER-MAJOR) readline: fix question still called after closed)

Qiulang
  • 10,295
  • 11
  • 80
  • 129

2 Answers2

1

I'm not 100% certain, but this excerpt just got too long for the comments so I had to add it as an answer. Because I have added it as an answer, I will continue to do some research to make sure that what I have written below is cannon.

First off, these streams are written in C++, and anyone familiar with C++ would tell you that the node.js streams sorta feel like C++ streams when writing them, with that being said; I learned in a C++ class a few years back, that duplex streams are a single object that's an abstraction of two streams. What that means, is even though a duplex stream is treated as a single entity, at a lower level of abstraction, a level that is minimally accessible in the Node.js RTE, the duplex stream is two objects.

There are some option values, that can be configured via the duplex constructor's options parameter that allows you to configure how the duplex object makes use of both streams. There is one option that is extremely relevant to the current topic, it's named allowHalfOpen.





Node.js Docs: allowHalfOpen parameter

        If allowHalfOpen is set to false, then the stream will automatically end the writable side when the readable side ends. — Default: true

          — The above is according to Node.js documentation

An important part of what is included in the docs-quote above, is the value the option defaults to, which is TRUE.




The allowHalfOpen Property

If you read the Class: stream.Duplex documentation, it allows for the setting of allowHalfOpen, but as a property, not as a constructor's parameter.

Node.js Docs on: allowHalfOpen property
duplex.allowHalfOpen <boolean>

          If false then the stream will automatically end the writable side when the readable side ends. Set initially by the allowHalfOpen constructor option, which defaults to false.

          This can be changed manually to change the half-open behavior of an existing Duplex stream instance, but must be changed before the 'end' event is emitted.

          — The above is according to Node.js documentation




I don't really understand why...

Class: stream.Duplex, the version that is auto-created by Node, and is made accessible through the node process object...

  • process.stdin
  • process.stdout

...defaults the allowHalfOpen property to FALSE, whereas the default value when using...

  • new stream.Duplex(options)

is TRUE.


To Summarize

You can try turning the allow half open property to false. Then try closing the stream. You could also try creating a new instance of the readline object, and set the allowHalfOpen open options parameter to FALSE. Then try the same code in your question, but with your own implementation of Stream used by the readline object.

Depending on the outcome I may delete this answer in the future. Also I myself plan on playing with this to try and see if I can offer a snippet that will help gain a cannon understanding of why the behavior is what it is.

From what I remember in a CS class the language we wrote was C++, which is what these streams are actually written in)duplex stream. I ran into this same conundrum a while back, and found that when controlling the behavior of a duplex stream, requires being aware of both the readable, and writable stream. (If my memory is serving well right now) in a C++ class we learned that duplex streams are treated as 1 stream, when they are in fact two streams. This means that one side of the stream can be open, while one side is closed. which by default, is allowed to be set to half open. You can try setting the streams.

Pang
  • 9,564
  • 146
  • 81
  • 122
JΛYDΞV
  • 8,532
  • 3
  • 51
  • 77
0

Cross-posting the last part of my own answer from here:

I found a lovely answer here which offers a good solution.

Create the function:

const prompt = msg => {
  fs.writeSync(1, String(msg));
  let s = '', buf = Buffer.alloc(1);
  while(buf[0] - 10 && buf[0] - 13)
    s += buf, fs.readSync(0, buf, 0, 1, 0);
  return s.slice(1);
};

Use it:

const result = prompt('Input something: ');
console.log('Your input was: ' + result);

This allows taking user input without the terminal hanging afterwards.

This solution is not perfect however - it intentionally blocks the main thread while waiting for user input, meaning you cannot run other functions in the background while waiting for user input. In my mind user input should be thread-blocking in most cases anyway, so this solution works very well for me personally.

aggregate1166877
  • 2,196
  • 23
  • 38