-
Notifications
You must be signed in to change notification settings - Fork 29.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
globals: introduce prompt
on global
#38552
Conversation
17c932a
to
fcde071
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally don't think it's a good idea to implement a sync API for this – I get the point regarding web compatibility, but async would make much more sense for this feature IMHO.
I'm adding a request change to reflect my personal -1, and to make sure this doesn't land too quickly; the topic of introducing legacy web APIs probably deserves a discussion as a whole and maybe a decision from the TSC.
@@ -333,5 +333,6 @@ module.exports = { | |||
btoa: 'readable', | |||
atob: 'readable', | |||
performance: 'readable', | |||
prompt: 'readable' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
prompt: 'readable' | |
prompt: 'readable', |
@@ -246,6 +247,60 @@ if (!config.noBrowserGlobals) { | |||
return performance; | |||
}); | |||
|
|||
defineOperation(globalThis, 'prompt', function prompt( | |||
message = 'prompt', | |||
defaultValue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
defaultValue | |
defaultValue = null |
validateString(defaultValue); | ||
} | ||
|
||
defaultValue ??= null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
defaultValue ??= null; |
if (charaBuf[0] === LF) { | ||
break; | ||
} | ||
res.push(charaBuf[0]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
res.push(charaBuf[0]); | |
ArrayPrototypePush(res, charaBuf[0]); |
I'd be +1 on this. I get the concerns about it not being async but the point about web compat is sound. One thing we might also consider is opening a proposal upstream to add an async version of the |
returns `null`. | ||
If default value is given and the user's input is empty string, it returns | ||
`defaultValue`. | ||
Otherwise, it returns the user's input as `string`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should describe in here whether this method blocks the progression of the event loop or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-1 Things like this are easily implemented in userland (there are even await
-able examples in the readline
docs currently) and don't belong in node core IMO (especially as a global).
const common = require('../common'); | ||
const assert = require('assert'); | ||
|
||
(async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no reason for this to be wrapped in an async function.
FWIW, I disagree entirely. Where such functions exist across multiple platforms and runtimes, and where there is similar functionality already included, it makes sense to conform to the standard API vs. continuing to rely on a non-standard Node.js specific API. |
fs.openSync('/dev/tty', 'r'); | ||
const res = []; | ||
|
||
while (true) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My preference would be to implement this in terms of the functionality already available in readline
and repl
if possible. Also, burying this implementation into internal/bootstrap/node.js
makes it more difficult to find and maintain, especially given that it differs from the similar functionality in readline
.
The use of sync fs methods can also be problematic here.
I'm +1 on the idea of getting this into core but I don't think the impl as it is now is the right way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it could go into tty.js
– maybe as a method on the tty.ReadStream
prototype, so it can be used like this:
const answer = await process.stdin.prompt('Question?\n');
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd try to implement it on the top of readline
/repl
.
// Mimic implementation of `prompt` of deno | ||
function readFromStdInSync() { | ||
const fs = require('fs'); | ||
const LF = '\n'.charCodeAt(0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const LF = '\n'.charCodeAt(0); | |
const LF = StringPrototypeCharCodeAt('\n', 0); |
Use primordials for consistency.
function readFromStdInSync() { | ||
const fs = require('fs'); | ||
const LF = '\n'.charCodeAt(0); | ||
const CR = '\r'.charCodeAt(0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const CR = '\r'.charCodeAt(0); | |
const CR = StringPrototypeCharCodeAt('\r', 0); |
Same case as above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also don't think we should implement user-facing web APIs, so I'm -1 as well.
Web compatibility is a nice thing for libraries, because they are made to be reusable, so I can see why we add stuff like atob
that should not be used at all, but compatibility of UI code is way out of scope for Node.js, imo. People are just not going to write (non-trivial) applications that run in both Node.js and the browser, and if they really want to, then I agree with @mscdex that that is decidedly a problem for userland to solve.
(I also agree in that the readline APIs are better for this purpose.)
break; | ||
} | ||
if (charaBuf[0] === CR) { | ||
const bytesRead = fs.readSync(stdfd, charaBuf, 0, 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This kind of implementation is going to be inherently flaky in cases in which process.stdin
is also used for reading.
if we add this can we also add blocking xhr? |
I'd agree if Node.js did not already include UI code that could have been easily implemented as userland code. If the intent is that UI code in Node.js really ought to be the realm of userland and the module ecosystem, then perhaps what we really ought to be doing is de-emphasizing userland use of readline and repl as reusable APIs. (Not saying deprecate or remove them, but mark them legacy and stop evolving them) |
@jasnell Yes, I would love it if we could separate out readline and/or REPL, put them into their own npm packages, and just vendor them in or something like that for our internal usage and encourage people to use the npm packages (the way we handled node-inspect up until recently, basically). (This is not just something that I think would be theoretically nice enough to have – it would also be useful just for my day-to-day work, and probably enough so that I might be able to some company time allocated just to help make that happen. It’s somewhat ouf of scope for this PR discussion here, but getting a feeling for whether @nodejs/tsc would be interested in this at all would be nice.) |
I'd separate my rationale into two questions, that
For question 1, I can’t come up with a reason why not to implement it in core.
For question 2, While I think
While it is true that users can easily(?) implement interactive CLI application with current facilities, it needs a lot of boilerplate code. It is unintuitive for beginner and cumbersome for even advanced users.
I understand that Node.js has fundamental difference with Browser. Some Web APIs wouldn't make sense or deems as “legacy” in the context of Node.js. But my first intention is to facilitate one of the most common tasks. It seems that I want to make Node.js more web-compat by introducing more Web APIs. Really, I don't mean that. That is a consequence but not a intention. |
I think the best way to achieve that for this specific feature would be better documentation, e.g. not bury the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm ok in adding some method to easily prompt the user.
I'm -1 in adding a synchronous, global method to do so.
Considering top-level await, the following is quite a nice API:
import { prompt } from 'tty'
await prompt('hello?')
Yeah, I think @mcollina’s suggestion would be a bit nicer. (Also, just from an API design perspective, this should be built on top of readline and accept at least a subset of readline’s options, i.e. input and output stream, whether to treat the input stream as a terminal, etc. – And generally, I think the readline module would be a better fit than the tty one, but that’s details.) |
Implementing as an async option with additional options gets us further from web compat but what we could do here is design the api with the intention of proposing it in upstream standards also. |
Author of the original issue here. First of all, thank you for taking the time to address the issue I raised, I was expecting it to be ignored but to see that this community cares about issues raised by its users this much really is heartwarming. I'm seeing some resistance to the idea of having an API for this at all by some people, with the argument being "Just make the user use the As time goes on, Javascript+NodeJS is a combination that is moving from being a tool that is used exclusively for developing serverside applications for the web to more of a general-purpose programming language like C, Python, and Java are. JS has gone from a "programming language for web-related stuff" to being used to write GUIs, Operating Systems, and even machine learning models. Like it or not, Javascript is going to become a completely general-purpose language that is taught to children in schools one day, and if we want that to happen we really need to lower the barrier of entry for beginners. Prompting the user for input from stdin is such a fundamental feature for any programming language that it is one of the first things that are taught to beginners. Python has This is why I believe that it is imperative to include a method to easily prompt the user for input. As for the details of how it should be implemented is up to you guys. I would personally prefer if it were a synchronous and global function (like Python's |
@@ -0,0 +1,2 @@ | |||
sushi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you test with non-ASCII characters please?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have tried one time, but it seems throwing confusing error. I'd try again in my next PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My guess is that is overflows the Uint8Array
, as non-ASCII char typically takes more than one byte.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that implementation works in REPL well, but fails in test. My guess is that pseudo-tty
test may not support non-ASCII character as input or output? I will fill an issue, if I can confirm that with current API.
REPL
Welcome to Node.js v17.0.0-pre.
Type ".help" for more information.
> prompt("hello")
hello
寿司
'寿司'
>
Test
[00:00|% 0|+ 0|- 0]: release test-prompt match failed
line=0
expect=^\�\�\�\�\�\�$
actual=寿司
=== release test-prompt ===
Path: pseudo-tty/test-prompt
寿司
what your favorite food?
Command: /System/Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python /Users/qingyudeng/projects/node/test/pseudo-tty/pty_helper.py out/Release/node /Users/qingyudeng/projects/node/test/pseudo-tty/test-prompt.js
[00:00|% 100|+ 0|- 1]: Done
Resolved, it is problem with python 2, using python 3 works fine.
Most of our docs would benefit from a longer overview and quick start section at the top that would show typical simple usage, highlighting the best parts of the API. Then the reference section can follow, and it can always always always list things in alphabetical order (or some other order that eliminates human judgment or ambiguity from the ordering). |
Closing |
Background
When writing a CLI application on Node.js, there is a pain point when ask user for input. Without third-party module, currently the best way to ask user input is to use
readline
module.However, it could be somehow "too complex" for some simple use case(like CLI-based guessing number game or just simply ask people to choose a project name in a script).
In this PR, it adds a new global function
prompt
, which accepts amessage
anddefaultValue
. It echoesmessage
to user, and returns user input synchronously. Example is shown as followingAPI Design Consideration
The origin issue suggested something like
readline.input
orprocess.input
. Thanks to @artembykov's suggestion, I thinkprompt
onglobal
would be ideal solution to given problem.prompt
method is standardzied in browser side, and also is implemented onglobal
in deno. Introducingprompt
will not make API in JS world scattering, while benifits user.Fixes: #37925