Модуль readline в Node.js: чтение ввода построчно

Содержание

О проверке кода. Примеры не запускались в этой сессии (по договорённости — фокус на контенте, а не прогонах). Говорим прямо, а не ставим бейдж «проверено».

readline в Node.js решает две задачи: запросить ввод у пользователя в консоли и прочитать большой файл построчно. Обе — частые, обе он делает без внешних зависимостей. Разберём современный подход.

Ввод пользователя: промис-версия

Современный способ — модуль readline/promises с await:

const readline = require('node:readline/promises');

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

const name = await rl.question('Как вас зовут? ');
const age = await rl.question('Сколько вам лет? ');

console.log(`Привет, ${name}! Вам ${age}.`);
rl.close();   // обязательно — иначе процесс не завершится

rl.question показывает вопрос и ждёт ответа пользователя. Промис-версия (node:readline/promises) возвращает промис, поэтому читается как синхронный код через await — без вложенных колбэков. Интерфейс создаётся на process.stdin/process.stdout. Важно вызвать rl.close(), иначе процесс останется висеть, ожидая ввода.

Старая колбэк-версия (node:readline) тоже работает, но await заметно чище:

// старый стиль — колбэк
rl.question('Имя? ', (answer) => {
  console.log(answer);
  rl.close();
});

Чтение файла построчно

Главное серьёзное применение — обработка больших файлов без загрузки в память:

const readline = require('node:readline');
const fs = require('node:fs');

const rl = readline.createInterface({
  input: fs.createReadStream('huge.log'),   // поток чтения файла
  crlfDelay: Infinity,                       // корректно с \r\n
});

let lineCount = 0;
for await (const line of rl) {               // перебираем строки по одной
  lineCount++;
  if (line.includes('ERROR')) {
    console.log(`Строка ${lineCount}: ${line}`);
  }
}
console.log(`Всего строк: ${lineCount}`);

createInterface на потоке чтения файла выдаёт файл по одной строке, а for await их перебирает. Файл при этом не загружается целиком — в памяти в каждый момент лишь одна строка. Это работает и для гигабайтных логов, которые в память просто не поместятся.

Почему не readFile

readFile против readline для больших файлов

fs.readFile — весь файл сразу

Для больших файлов не годится

  • Грузит файл целиком в память
  • Файл 4 ГБ → нужно 4 ГБ ОЗУ
  • Больше памяти → падение
  • Ок только для мелких файлов

readline — по строке

Файл любого размера

  • В памяти одна строка за раз
  • Файл любого размера — ок
  • Обработка на лету
  • Основа для логов и CSV

fs.readFile загружает файл в память полностью. Для конфига в пару килобайт это нормально, но файл больше доступной памяти так не прочитать — процесс упадёт с нехваткой памяти. readline через поток держит лишь текущую строку, поэтому справляется с файлами любого размера. Правило: мелкий файл — readFile, большой построчно — readline.

Практический пример: простое меню

const readline = require('node:readline/promises');
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });

let running = true;
while (running) {
  const cmd = await rl.question('> ');
  if (cmd === 'exit') {
    running = false;
  } else if (cmd === 'time') {
    console.log(new Date().toLocaleTimeString('ru-RU'));
  } else {
    console.log('Неизвестная команда');
  }
}
rl.close();

Цикл с await rl.question — основа интерактивных консольных утилит: спросить, обработать, повторить. Это удобно для небольших инструментов, генераторов конфигов, обучающих скриптов — там, где нужен диалог с пользователем без веб-интерфейса.

Когда это нужно

  • интерактивные CLI-скрипты — спросить имя проекта, подтвердить действие, выбрать вариант;
  • построчная обработка логов и CSV — фильтр, подсчёт, поиск по гигабайтным файлам;
  • простые REPL-подобные инструменты — свой цикл «команда → ответ».

Для сложных CLI с флагами и подкомандами берут библиотеки, а util.parseArgs разбирает аргументы командной строки. Но для чтения ввода и построчной обработки файлов readline — точный инструмент из коробки.


Смежные темы: потоки — как читается файл; process и stdin; файловая система — readFile против потока. Полный список — в уроках Node.js.

Частые вопросы

Что делает модуль readline?
Позволяет читать данные построчно из потока: ввода пользователя в консоли или содержимого файла. Он выдаёт по одной строке за раз, что удобно для интерактивных скриптов и обработки больших файлов без загрузки целиком.
Как запросить ввод пользователя в консоли?
Создать интерфейс readline на process.stdin и process.stdout, затем вызвать rl.question с текстом вопроса. В промис-версии из readline/promises это делается через await rl.question, что читается как обычный синхронный код.
Как прочитать большой файл построчно?
Создать поток чтения файла и передать его в readline.createInterface, затем перебрать строки через for await. Так файл обрабатывается по одной строке, а не загружается в память целиком — подходит для гигабайтных логов.
Почему readline лучше, чем читать весь файл сразу?
Чтение всего файла через readFile загружает его в память полностью, что невозможно для файлов больше доступной памяти. readline через поток держит в памяти лишь одну строку за раз, поэтому справляется с файлами любого размера.
Есть ли у readline промис-версия?
Да, модуль node:readline/promises даёт rl.question, возвращающий промис, что позволяет использовать await вместо колбэков. Это современный и более читаемый способ запрашивать ввод в интерактивных скриптах.