要在 Node.js 中读取用户输入,在 process.stdin 上创建一个 readline 接口,然后用 rl.question() 提问。Node.js 没有 prompt():浏览器里的版本会打开一个对话框,而终端程序没有对话框。现代的写法使用 readline/promises,其中 await rl.question('Name? ') 会返回用户输入的那一行。用完之后要关闭接口,否则程序会一直等待更多输入。
question() 打印出提示,等待一行输入,然后返回它(不带换行符),所以程序打印了 Next year you will be 37.。要问多个问题,就依次 await 多个 question()。
使用回调的 readline question()
最初的 API 在每个 Node.js 版本中都有,它把答案传给一个回调函数。问题嵌套,回调也跟着嵌套:
它先打印提示,然后打印 You picked blue.。答案是一个普通字符串,所以比较之前要先 trim:否则用户在单词后面多打了一个空格,得到的就是 'blue ',它不等于 'blue'。
逐行读取输入
要一直处理每一行直到输入结束,就用 for await 遍历这个接口。程序就是这样读取用户输入的一个列表(以 Ctrl+D 结束),或者用 node index.js < numbers.txt 通过管道传入的文件。旧代码用事件做同样的事:每一行触发 rl.on('line', fn),结束时触发 rl.on('close', fn);这个循环就是把这对事件从上到下写出来:
它打印了 3 numbers, total 49,并且循环在输入结束时自己停止了,所以不需要 close()。再加第四行输入 1,结果就变成 4 numbers, total 50。
一次读取全部 stdin
对于从另一个程序通过管道传入的输入(cat data.txt | node index.js),一次读取全部内容更简单。文件描述符 0 就是标准输入。在 Windows 上保存的文件,行尾可能是 \r\n,所以当输入可能来自那里时,用 /\r?\n/ 来分割:
它把三个物品打印成了一列。readFileSync(0) 会阻塞,直到输入全部到达,这对于读取管道的脚本和测试都没有问题,但不适合交互式工具,因为用户要先把所有内容都输入完,才能看到任何提示。
该用哪一种
这四种方式读取的都是同一个标准输入。区别在于你的代码什么时候拿到文本:一次一个答案、一次一行,还是在最后一次拿到全部。
| 需求 | 使用 |
|---|---|
| 按顺序问几个问题 | readline/promises 配合 await rl.question() |
| 支持 17 之前的 Node.js 版本 | readline 配合 question() 回调 |
| 每到一行就处理一行 | for await (const line of rl) |
| 从 stdin 读取整个管道或文件 | fs.readFileSync(0, 'utf8') |
| 程序启动前就已知的值 | 改用 process.argv |
常见错误
忘记 rl.close()。 接口会让标准输入保持打开,所以程序在最后一个问题之后永远不会退出。拿到所有答案后就关闭它。
把答案当成数字用。 和终端里输入的所有内容一样,答案是一个字符串。'36' + 1 的结果是 '361';要先用 Number() 转换。
把多个答案通过管道传给多个问题。 当输入来自管道而不是键盘时,readline 会一次读取全部内容,在对应的 question() 提问之前就到达的行会被丢掉。对于管道输入,请使用 for await 循环或 readFileSync(0)。
漏掉 output 选项。 没有 output: process.stdout,question() 就不会打印提示,用户只能盯着一个空白的终端。逐行读取的循环不需要它,因为它什么都不打印。
常见问题
为什么 Node.js 中 prompt 未定义?
prompt() 是浏览器的函数,它会打开一个对话框,而 Node.js 没有窗口可以打开它。在 Node.js 中请使用 readline,需要菜单和密码输入框时可以用 prompts 或 inquirer 这类包。
怎么读取密码而不显示出来?
readline 会回显每一个按键。prompts 或 @inquirer/password 这类包可以把输入遮住;自己实现的话,需要用 process.stdin.setRawMode(true) 把终端切换到原始模式,并处理每一个按键。
应该用 readline 还是 process.argv?
当值在程序启动之前就已知时,用 process.argv,这样程序可以在脚本里运行,不需要有人在场。对于一个接一个提问的交互式工具,用 readline。