readline (citire pe rânduri) în Node.js

Evenimente, stream-uri și date binare Node.js 0.1.98; readline/promises din 17.0 (stabil din 22.17 / 24.0)

Citește un fișier sau tastatura rând cu rând și pune întrebări utilizatorului în terminal (question).

Sintaxă
import readline from "node:readline";
import * as rlp from "node:readline/promises";
const rl = readline.createInterface({ input, output });
for await (const linie of rl) { ... }
await rl.question("Întrebare? ")

Ce face readline (citire pe rânduri)

readline.createInterface({ input }) împarte un stream de citire în rânduri. Cu for await (const linie of rl) citești un fișier oricât de mare rând cu rând, fără să-l încarci tot în memorie. Opțiunea crlfDelay: Infinity tratează corect sfârșitul de rând Windows (\r\n).

Pentru programe interactive, varianta din node:readline/promises are rl.question(text), care afișează întrebarea și întoarce o promisiune cu răspunsul utilizatorului. Ca intrare dai process.stdin, ca ieșire process.stdout. La final, apelează rl.close(), altfel programul așteaptă în continuare.

Parametri

Parametru Descriere
input Stream-ul citit (process.stdin, fs.createReadStream(...)).
output Unde se afișează întrebările (process.stdout).
crlfDelay Infinity pentru fișiere cu sfârșit de rând Windows.
rl.question(text) Afișează textul și așteaptă un rând (promisiune în readline/promises).
rl.close() Închide interfața.

Exemple

Exemplul 1: Un fișier mare, rând cu rând

import { createReadStream, writeFileSync } from "node:fs";
import readline from "node:readline";

writeFileSync("vanzari.csv", "oras,suma\nIași,1200\nCluj,800\nIași,450\nArad,300\n");

const rl = readline.createInterface({ input: createReadStream("vanzari.csv"), crlfDelay: Infinity });
const totaluri = {};
let antet = true;
for await (const linie of rl) {
  if (antet) { antet = false; continue; }
  const [oras, suma] = linie.split(",");
  totaluri[oras] = (totaluri[oras] ?? 0) + Number(suma);
}
console.log(totaluri);

Rezultat

{ 'Iași': 1650, Cluj: 800, Arad: 300 }

Exemplul 2: Întrebări în terminal

În terminal utilizatorul tastează răspunsul și apasă Enter; aici îl primește din intrarea de mai jos. Pentru mai multe întrebări, apelezi await rl.question(...) de mai multe ori.

import * as readline from "node:readline/promises";

const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
const raspuns = await rl.question("Ce notă ai luat la matematică? ");
rl.close();

const nota = Number(raspuns);
if (Number.isNaN(nota) || nota < 1 || nota > 10) console.log("\nNotă invalidă.");
else console.log(`\n${nota >= 5 ? "Ai promovat" : "Nu ai promovat"} cu nota ${nota}.`);

Ce scrie utilizatorul (intrare)

9

Rezultat

Ce notă ai luat la matematică? 
Ai promovat cu nota 9.

Greșeli frecvente

  • Uiți rl.close(): programul nu se termină, pentru că interfața încă ascultă tastatura.
  • Citești un fișier mare cu readFile și split("\n"): încarcă totul în memorie. readline cu un stream merge pe fișiere de orice mărime.
  • Trimiți răspunsurile printr-un pipe (printf "Ana\n25\n" | node app.mjs) unui program cu mai multe question: rândurile sosite înainte de a doua întrebare se pierd, iar programul se oprește cu codul 13 (await neterminat). Pentru intrare din pipe, citește rândurile cu for await (const linie of rl).
  • Folosești rl.question din node:readline (cel vechi) cu await: acolo primește callback. Importă din node:readline/promises.

Comenzi înrudite

  • stream.Readable: Stream-ul din care citești date bucată cu bucată: fișiere, cereri HTTP, stdin. Readable.from îl creează din orice iterabil.
  • process.argv: Tabloul cu argumentele din linia de comandă: process.argv.slice(2) dă ce a scris utilizatorul după numele scriptului.
  • util.parseArgs: Citește opțiunile din linia de comandă (–port 8080, -v, –fara-culori) fără pachete ca yargs sau commander.
  • fs.readFile: Citește tot conținutul unui fișier: ca text dacă dai codificarea („utf8”), altfel ca Buffer cu octeții bruți.