diff options
author | Antoine du Hamel <duhamelantoine1995@gmail.com> | 2021-04-27 17:14:39 +0300 |
---|---|---|
committer | Antoine du Hamel <duhamelantoine1995@gmail.com> | 2021-09-17 00:16:37 +0300 |
commit | 8122d243ae010f3a5c1d50e4d0ef6374d4e407b4 (patch) | |
tree | 3ab213dec7b55944afcc96ae0c271bcc1276e3a1 /doc | |
parent | 592d1c3d4442c3b0d6d747a7dc02797e63f97702 (diff) |
readline: introduce promise-based API
PR-URL: https://github.com/nodejs/node/pull/37947
Fixes: https://github.com/nodejs/node/issues/37287
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Robert Nagy <ronagy@icloud.com>
Diffstat (limited to 'doc')
-rw-r--r-- | doc/api/readline.md | 467 |
1 files changed, 417 insertions, 50 deletions
diff --git a/doc/api/readline.md b/doc/api/readline.md index 8f056c942e3..b886c5b2bb6 100644 --- a/doc/api/readline.md +++ b/doc/api/readline.md @@ -7,22 +7,48 @@ <!-- source_link=lib/readline.js --> The `readline` module provides an interface for reading data from a [Readable][] -stream (such as [`process.stdin`][]) one line at a time. It can be accessed -using: +stream (such as [`process.stdin`][]) one line at a time. -```js +To use the promise-based APIs: + +```mjs +import * as readline from 'node:readline/promises'; +``` + +```cjs +const readline = require('readline/promises'); +``` + +To use the callback and sync APIs: + +```mjs +import * as readline from 'node:readline'; +``` + +```cjs const readline = require('readline'); ``` The following simple example illustrates the basic use of the `readline` module. -```js +```mjs +import * as readline from 'node:readline/promises'; +import { stdin as input, stdout as output } from 'process'; + +const rl = readline.createInterface({ input, output }); + +const answer = await rl.question('What do you think of Node.js? '); + +console.log(`Thank you for your valuable feedback: ${answer}`); + +rl.close(); +``` + +```cjs const readline = require('readline'); +const { stdin: input, stdout: output } = require('process'); -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout -}); +const rl = readline.createInterface({ input, output }); rl.question('What do you think of Node.js? ', (answer) => { // TODO: Log the answer in a database @@ -36,16 +62,18 @@ Once this code is invoked, the Node.js application will not terminate until the `readline.Interface` is closed because the interface waits for data to be received on the `input` stream. -## Class: `Interface` +<a id='readline_class_interface'></a> +## Class: `InterfaceConstructor` <!-- YAML added: v0.1.104 --> * Extends: {EventEmitter} -Instances of the `readline.Interface` class are constructed using the -`readline.createInterface()` method. Every instance is associated with a -single `input` [Readable][] stream and a single `output` [Writable][] stream. +Instances of the `InterfaceConstructor` class are constructed using the +`readlinePromises.createInterface()` or `readline.createInterface()` method. +Every instance is associated with a single `input` [Readable][] stream and a +single `output` [Writable][] stream. The `output` stream is used to print prompts for user input that arrives on, and is read from, the `input` stream. @@ -56,18 +84,18 @@ added: v0.1.98 The `'close'` event is emitted when one of the following occur: -* The `rl.close()` method is called and the `readline.Interface` instance has +* The `rl.close()` method is called and the `InterfaceConstructor` instance has relinquished control over the `input` and `output` streams; * The `input` stream receives its `'end'` event; * The `input` stream receives <kbd>Ctrl</kbd>+<kbd>D</kbd> to signal end-of-transmission (EOT); * The `input` stream receives <kbd>Ctrl</kbd>+<kbd>C</kbd> to signal `SIGINT` and there is no `'SIGINT'` event listener registered on the - `readline.Interface` instance. + `InterfaceConstructor` instance. The listener function is called without passing any arguments. -The `readline.Interface` instance is finished once the `'close'` event is +The `InterfaceConstructor` instance is finished once the `'close'` event is emitted. ### Event: `'line'` @@ -220,12 +248,12 @@ The `'SIGTSTP'` event is _not_ supported on Windows. added: v0.1.98 --> -The `rl.close()` method closes the `readline.Interface` instance and +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 `readline.Interface` instance. +from being emitted by the `InterfaceConstructor` instance. ### `rl.pause()` <!-- YAML @@ -236,7 +264,7 @@ The `rl.pause()` method pauses the `input` stream, allowing it to be resumed later if necessary. Calling `rl.pause()` does not immediately pause other events (including -`'line'`) from being emitted by the `readline.Interface` instance. +`'line'`) from being emitted by the `InterfaceConstructor` instance. ### `rl.prompt([preserveCursor])` <!-- YAML @@ -246,14 +274,14 @@ added: v0.1.98 * `preserveCursor` {boolean} If `true`, prevents the cursor placement from being reset to `0`. -The `rl.prompt()` method writes the `readline.Interface` instances configured +The `rl.prompt()` method writes the `InterfaceConstructor` instances configured `prompt` to a new line in `output` in order to provide a user with a new location at which to provide input. When called, `rl.prompt()` will resume the `input` stream if it has been paused. -If the `readline.Interface` was created with `output` set to `null` or +If the `InterfaceConstructor` was created with `output` set to `null` or `undefined` the prompt is not written. ### `rl.question(query[, options], callback)` @@ -276,7 +304,7 @@ function passing the provided input as the first argument. When called, `rl.question()` will resume the `input` stream if it has been paused. -If the `readline.Interface` was created with `output` set to `null` or +If the `InterfaceConstructor` was created with `output` set to `null` or `undefined` the `query` is not written. The `callback` function passed to `rl.question()` does not follow the typical @@ -377,7 +405,7 @@ If `key` is specified, `data` is ignored. When called, `rl.write()` will resume the `input` stream if it has been paused. -If the `readline.Interface` was created with `output` set to `null` or +If the `InterfaceConstructor` was created with `output` set to `null` or `undefined` the `data` and `key` are not written. ```js @@ -406,13 +434,13 @@ changes: Create an `AsyncIterator` object that iterates through each line in the input stream as a string. This method allows asynchronous iteration of -`readline.Interface` objects through `for await...of` loops. +`InterfaceConstructor` objects through `for await...of` loops. Errors in the input stream are not forwarded. If the loop is terminated with `break`, `throw`, or `return`, [`rl.close()`][] will be called. In other words, iterating over a -`readline.Interface` will always consume the input stream fully. +`InterfaceConstructor` will always consume the input stream fully. Performance is not on par with the traditional `'line'` event API. Use `'line'` instead for performance-sensitive applications. @@ -502,7 +530,346 @@ Returns the real position of the cursor in relation to the input prompt + string. Long input (wrapping) strings, as well as multiple line prompts are included in the calculations. -## `readline.clearLine(stream, dir[, callback])` +## Promises API +<!-- YAML +added: REPLACEME +--> + +### Class: `readlinePromises.Interface` +<!-- YAML +added: REPLACEME +--> + +* Extends: {readline.InterfaceConstructor} + +Instances of the `readlinePromises.Interface` class are constructed using the +`readlinePromises.createInterface()` method. Every instance is associated with a +single `input` [Readable][] stream and a single `output` [Writable][] stream. +The `output` stream is used to print prompts for user input that arrives on, +and is read from, the `input` stream. + +#### `rl.question(query[, options])` +<!-- YAML +added: REPLACEME +--> + +* `query` {string} A statement or query to write to `output`, prepended to the + prompt. +* `options` {Object} + * `signal` {AbortSignal} Optionally allows the `question()` to be canceled + using an `AbortController`. +* Returns: {Promise} A promise that is fulfilled with the user's + input in response to the `query`. + +The `rl.question()` method displays the `query` by writing it to the `output`, +waits for user input to be provided on `input`, then invokes the `callback` +function passing the provided input as the first argument. + +When called, `rl.question()` will resume the `input` stream if it has been +paused. + +If the `readlinePromises.Interface` was created with `output` set to `null` or +`undefined` the `query` is not written. + +Example usage: + +```mjs +const answer = await rl.question('What is your favorite food? '); +console.log(`Oh, so your favorite food is ${answer}`); +``` + +Using an `AbortController` to cancel a question. + +```mjs +const ac = new AbortController(); +const signal = ac.signal; + +const answer = await rl.question('What is your favorite food? ', { signal }); +console.log(`Oh, so your favorite food is ${answer}`); + +signal.addEventListener('abort', () => { + console.log('The food question timed out'); +}, { once: true }); + +setTimeout(() => ac.abort(), 10000); +``` + +### Class: `readlinePromises.Readline` +<!-- YAML +added: REPLACEME +--> + +#### `new readlinePromises.Readline(stream)` +<!-- YAML +added: REPLACEME +--> + +* `stream` {stream.Writable} A [TTY][] stream. + +#### `rl.clearLine(dir)` +<!-- YAML +added: REPLACEME +--> + +* `dir` {integer} + * `-1`: to the left from cursor + * `1`: to the right from cursor + * `0`: the entire line +* Returns: this + +The `rl.clearLine()` method adds to the internal list of pending action an +action that clears current line of the associated `stream` in a specified +direction identified by `dir`. +You need to call `rl.commit()` to see the effect of this method. + +#### `rl.clearScreenDown()` +<!-- YAML +added: REPLACEME +--> + +* Returns: this + +The `rl.clearScreenDown()` method adds to the internal list of pending action an +action that clears the associated stream from the current position of the +cursor down. +You need to call `rl.commit()` to see the effect of this method. + +#### `rl.commit()` +<!-- YAML +added: REPLACEME +--> + +* Returns: {Promise} + +The `rl.commit()` method sends all the pending actions to the associated +`stream` and clears the internal list of pending actions. + +#### `rl.cursorTo(x[, y])` +<!-- YAML +added: REPLACEME +--> + +* `x` {integer} +* `y` {integer} +* Returns: this + +The `rl.cursorTo()` method adds to the internal list of pending action an action +that moves cursor to the specified position in the associated `stream`. +You need to call `rl.commit()` to see the effect of this method. + +#### `rl.moveCursor(dx, dy)` +<!-- YAML +added: REPLACEME +--> + +* `dx` {integer} +* `dy` {integer} +* Returns: this + +The `rl.moveCursor()` method adds to the internal list of pending action an +action that moves the cursor *relative* to its current position in the +associated `stream`. +You need to call `rl.commit()` to see the effect of this method. + +#### `rl.rollback()` +<!-- YAML +added: REPLACEME +--> + +* Returns: this + +The `rl.rollback` methods clears the internal list of pending actions without +sending it to the associated `stream`. + +### `readlinePromises.createInterface(options)` +<!-- YAML +added: REPLACEME +--> + +* `options` {Object} + * `input` {stream.Readable} The [Readable][] stream to listen to. This option + is *required*. + * `output` {stream.Writable} The [Writable][] stream to write readline data + to. + * `completer` {Function} An optional function used for Tab autocompletion. + * `terminal` {boolean} `true` if the `input` and `output` streams should be + treated like a TTY, and have ANSI/VT100 escape codes written to it. + **Default:** checking `isTTY` on the `output` stream upon instantiation. + * `history` {string[]} Initial list of history lines. This option makes sense + only if `terminal` is set to `true` by the user or by an internal `output` + check, otherwise the history caching mechanism is not initialized at all. + **Default:** `[]`. + * `historySize` {number} Maximum number of history lines retained. To disable + the history set this value to `0`. This option makes sense only if + `terminal` is set to `true` by the user or by an internal `output` check, + otherwise the history caching mechanism is not initialized at all. + **Default:** `30`. + * `removeHistoryDuplicates` {boolean} If `true`, when a new input line added + to the history list duplicates an older one, this removes the older line + from the list. **Default:** `false`. + * `prompt` {string} The prompt string to use. **Default:** `'> '`. + * `crlfDelay` {number} If the delay between `\r` and `\n` exceeds + `crlfDelay` milliseconds, both `\r` and `\n` will be treated as separate + end-of-line input. `crlfDelay` will be coerced to a number no less than + `100`. It can be set to `Infinity`, in which case `\r` followed by `\n` + will always be considered a single newline (which may be reasonable for + [reading files][] with `\r\n` line delimiter). **Default:** `100`. + * `escapeCodeTimeout` {number} The duration `readlinePromises` will wait for a + character (when reading an ambiguous key sequence in milliseconds one that + can both form a complete key sequence using the input read so far and can + take additional input to complete a longer key sequence). + **Default:** `500`. + * `tabSize` {integer} The number of spaces a tab is equal to (minimum 1). + **Default:** `8`. +* Returns: {readlinePromises.Interface} + +The `readlinePromises.createInterface()` method creates a new `readlinePromises.Interface` +instance. + +```js +const readlinePromises = require('readline/promises'); +const rl = readlinePromises.createInterface({ + input: process.stdin, + output: process.stdout +}); +``` + +Once the `readlinePromises.Interface` instance is created, the most common case +is to listen for the `'line'` event: + +```js +rl.on('line', (line) => { + console.log(`Received: ${line}`); +}); +``` + +If `terminal` is `true` for this instance then the `output` stream will get +the best compatibility if it defines an `output.columns` property and emits +a `'resize'` event on the `output` if or when the columns ever change +([`process.stdout`][] does this automatically when it is a TTY). + +#### Use of the `completer` function + +The `completer` function takes the current line entered by the user +as an argument, and returns an `Array` with 2 entries: + +* An `Array` with matching entries for the completion. +* The substring that was used for the matching. + +For instance: `[[substr1, substr2, ...], originalsubstring]`. + +```js +function completer(line) { + const completions = '.help .error .exit .quit .q'.split(' '); + const hits = completions.filter((c) => c.startsWith(line)); + // Show all completions if none found + return [hits.length ? hits : completions, line]; +} +``` + +The `completer` function can also returns a {Promise}, or be asynchronous: + +```js +async function completer(linePartial) { + await someAsyncWork(); + return [['123'], linePartial]; +} +``` + +## Callback API +<!-- YAML +added: v0.1.104 +--> + +### Class: `readline.Interface` +<!-- YAML +added: v0.1.104 +changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/37947 + description: The class `readline.Interface` now inherits from `Interface`. +--> + +* Extends: {readline.InterfaceConstructor} + +Instances of the `readline.Interface` class are constructed using the +`readline.createInterface()` method. Every instance is associated with a +single `input` [Readable][] stream and a single `output` [Writable][] stream. +The `output` stream is used to print prompts for user input that arrives on, +and is read from, the `input` stream. + +#### `rl.question(query[, options], callback)` +<!-- YAML +added: v0.3.3 +--> + +* `query` {string} A statement or query to write to `output`, prepended to the + prompt. +* `options` {Object} + * `signal` {AbortSignal} Optionally allows the `question()` to be canceled + using an `AbortController`. +* `callback` {Function} A callback function that is invoked with the user's + input in response to the `query`. + +The `rl.question()` method displays the `query` by writing it to the `output`, +waits for user input to be provided on `input`, then invokes the `callback` +function passing the provided input as the first argument. + +When called, `rl.question()` will resume the `input` stream if it has been +paused. + +If the `readline.Interface` was created with `output` set to `null` or +`undefined` the `query` is not written. + +The `callback` function passed to `rl.question()` does not follow the typical +pattern of accepting an `Error` object or `null` as the first argument. +The `callback` is called with the provided answer as the only argument. + +Example usage: + +```js +rl.question('What is your favorite food? ', (answer) => { + console.log(`Oh, so your favorite food is ${answer}`); +}); +``` + +Using an `AbortController` to cancel a question. + +```js +const ac = new AbortController(); +const signal = ac.signal; + +rl.question('What is your favorite food? ', { signal }, (answer) => { + console.log(`Oh, so your favorite food is ${answer}`); +}); + +signal.addEventListener('abort', () => { + console.log('The food question timed out'); +}, { once: true }); + +setTimeout(() => ac.abort(), 10000); +``` + +If this method is invoked as it's util.promisify()ed version, it returns a +Promise that fulfills with the answer. If the question is canceled using +an `AbortController` it will reject with an `AbortError`. + +```js +const util = require('util'); +const question = util.promisify(rl.question).bind(rl); + +async function questionExample() { + try { + const answer = await question('What is you favorite food? '); + console.log(`Oh, so your favorite food is ${answer}`); + } catch (err) { + console.error('Question rejected', err); + } +} +questionExample(); +``` + +### `readline.clearLine(stream, dir[, callback])` <!-- YAML added: v0.7.7 changes: @@ -524,7 +891,7 @@ changes: The `readline.clearLine()` method clears current line of given [TTY][] stream in a specified direction identified by `dir`. -## `readline.clearScreenDown(stream[, callback])` +### `readline.clearScreenDown(stream[, callback])` <!-- YAML added: v0.7.7 changes: @@ -542,7 +909,7 @@ changes: The `readline.clearScreenDown()` method clears the given [TTY][] stream from the current position of the cursor down. -## `readline.createInterface(options)` +### `readline.createInterface(options)` <!-- YAML added: v0.1.98 changes: @@ -646,7 +1013,7 @@ If you want your application to exit without waiting for user input, you can process.stdin.unref(); ``` -### Use of the `completer` function +#### Use of the `completer` function The `completer` function takes the current line entered by the user as an argument, and returns an `Array` with 2 entries: @@ -674,7 +1041,7 @@ function completer(linePartial, callback) { } ``` -## `readline.cursorTo(stream, x[, y][, callback])` +### `readline.cursorTo(stream, x[, y][, callback])` <!-- YAML added: v0.7.7 changes: @@ -694,13 +1061,33 @@ changes: The `readline.cursorTo()` method moves cursor to the specified position in a given [TTY][] `stream`. +### `readline.moveCursor(stream, dx, dy[, callback])` +<!-- YAML +added: v0.7.7 +changes: + - version: v12.7.0 + pr-url: https://github.com/nodejs/node/pull/28674 + description: The stream's write() callback and return value are exposed. +--> + +* `stream` {stream.Writable} +* `dx` {number} +* `dy` {number} +* `callback` {Function} Invoked once the operation completes. +* Returns: {boolean} `false` if `stream` wishes for the calling code to wait for + the `'drain'` event to be emitted before continuing to write additional data; + otherwise `true`. + +The `readline.moveCursor()` method moves the cursor *relative* to its current +position in a given [TTY][] `stream`. + ## `readline.emitKeypressEvents(stream[, interface])` <!-- YAML added: v0.7.7 --> * `stream` {stream.Readable} -* `interface` {readline.Interface} +* `interface` {readline.InterfaceConstructor} The `readline.emitKeypressEvents()` method causes the given [Readable][] stream to begin emitting `'keypress'` events corresponding to received input. @@ -720,26 +1107,6 @@ if (process.stdin.isTTY) process.stdin.setRawMode(true); ``` -## `readline.moveCursor(stream, dx, dy[, callback])` -<!-- YAML -added: v0.7.7 -changes: - - version: v12.7.0 - pr-url: https://github.com/nodejs/node/pull/28674 - description: The stream's write() callback and return value are exposed. ---> - -* `stream` {stream.Writable} -* `dx` {number} -* `dy` {number} -* `callback` {Function} Invoked once the operation completes. -* Returns: {boolean} `false` if `stream` wishes for the calling code to wait for - the `'drain'` event to be emitted before continuing to write additional data; - otherwise `true`. - -The `readline.moveCursor()` method moves the cursor *relative* to its current -position in a given [TTY][] `stream`. - ## Example: Tiny CLI The following example illustrates the use of `readline.Interface` class to |