rl.close()

Closes a readline interface, releasing its input and output streams and emitting the 'close' event.

Signature
rl.close()
  • Added in Node.js v0.1.98

Return value

undefinedNothing. The 'close' event is emitted once the interface has relinquished the input and output streams.

Errors

Does not throw. Calling it more than once is a no-op after the interface is already closed.

rl.close() ends the interface: it stops managing the streams and emits 'close'. The same event fires on its own when the input stream ends, when the user presses Ctrl+D, or when Ctrl+C arrives and no 'SIGINT' listener is registered.

When to reach for it

  • A CLI has all the answers it needs and should stop holding process.stdin open.
  • You found the record you were scanning for and want to abandon the rest of the file.
  • You are treating 'close' as "end of input", which is where the final tally belongs.

What trips people up

Closing does not immediately silence the interface. Lines already buffered from the input stream are still delivered after 'close' has fired, as the example below shows. If you must ignore them, set a flag in the 'close' handler and check it in the 'line' handler.

Closing also does not destroy options.input. When the input is a file stream you may still want input.destroy(); when it is process.stdin, forgetting to close is the usual reason a finished CLI hangs instead of exiting, because the open stdin handle keeps the event loop alive.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Exit an interactive prompt when the user types 'quit'
  • Stop scanning a log file once the first fatal error is found
  • Release process.stdin so a finished CLI actually exits
  • Run final reporting from a single 'close' handler

Frequently asked questions

Why does my Node CLI hang after reading input?

An open readline interface keeps process.stdin referenced, and that handle keeps the event loop alive. Call rl.close() once you have the input you need, or listen for 'close' and exit there.

Does rl.close() stop the 'line' event immediately?

No. Data already buffered from the input stream is still turned into 'line' events after 'close' has fired. Set a flag inside the 'close' handler and return early from the 'line' handler when it is set.

Does rl.close() close the underlying file stream?

It relinquishes control of the input and output streams but does not destroy them. For a file stream that you want released early, call input.destroy() as well as rl.close().

More examples

Ignoring lines that arrive after close

The done flag discards the buffered lines readline delivers after close(), so firstError holds the first FATAL line and not a later one. input.destroy() releases the file descriptor, which rl.close() on its own does not do.

Example · javascript
import fs from 'node:fs';
import readline from 'node:readline';
import { once } from 'node:events';

const input = fs.createReadStream('events.log');
const rl = readline.createInterface({ input, crlfDelay: Infinity });

let done = false;
let firstError = null;

rl.on('line', (line) => {
  if (done) return;
  if (line.startsWith('FATAL')) {
    firstError = line;
    done = true;
    rl.close();
    input.destroy();
  }
});

rl.on('close', () => console.log('first fatal:', firstError ?? 'none'));

await once(rl, 'close');

Letting the user close the prompt

With a 'SIGINT' listener registered, Ctrl+C no longer closes the interface automatically — the handler decides. Typing quit, pressing Ctrl+D, or confirming the SIGINT prompt all end up in the same 'close' handler.

Example · javascript
const readline = require('node:readline');

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  prompt: 'db> ',
});

rl.prompt();

rl.on('line', (line) => {
  if (line.trim() === 'quit') {
    rl.close();
    return;
  }
  console.log(`ran: ${line.trim()}`);
  rl.prompt();
});

rl.on('SIGINT', () => {
  rl.question('Really exit? (y/N) ', (answer) => {
    if (answer.match(/^y(es)?$/i)) rl.close();
    else rl.prompt();
  });
});

rl.on('close', () => console.log('\nsession ended'));

Closing via an AbortSignal instead

Aborting the signal has the same effect as calling rl.close(), which makes a timeout or a shared cancellation token able to stop the read without the reading code holding a reference to the interface.

Example · javascript
import fs from 'node:fs';
import readline from 'node:readline';
import { once } from 'node:events';

const ac = new AbortController();
const rl = readline.createInterface({
  input: fs.createReadStream('stream.txt'),
  crlfDelay: Infinity,
  signal: ac.signal,
});

setTimeout(() => ac.abort(), 1000).unref();

let n = 0;
rl.on('line', () => n++);
await once(rl, 'close');
console.log('read', n, 'lines before the deadline');

Discussion

  • Be the first to comment on this lesson.