IlmHamroh
JavaScript Full-stack/24-qism. Node.js23/55-dars18 daqiqa
Mundarija (28)

Node.js readline: terminalda savol-javob va faylni qatorma-qator o'qish

Qisqacha: node:readline — baytlar oqimini qatorlarga bo'ladigan modul. Uning ikki vazifasi bor: foydalanuvchidan terminalda so'rash (rl.question) va istalgan oqimni — faylni, stdinni — qatorma-qator o'qish (for await (const qator of rl)). Dastur ikki xil muhitda ishga tushadi: odam yozayotgan terminal (TTY) va boshqa dasturdan keladigan quvur (pipe). Biz o'lchadik: quvurda rl.question ikkinchi javobni yo'qotadi, for await esa hech narsani yo'qotmaydi.

Bu darsda

  • createInterface bilan oqimni qatorlarga bo'lasiz va for await bilan o'qiysiz.
  • readline/promises dagi rl.question bilan savol berasiz va uning quvurdagi tuzog'ini bilasiz.
  • process.stdin.isTTY bilan "odam yozyaptimi yoki quvurmi" ni aniqlaysiz.
  • Taklif (prompt), tarix va Tab bilan to'ldirish (completer) qanday sozlanishini ko'rasiz.
  • valyuta ga interaktiv rejim qo'shasiz: valyuta i.

Oldin bilishingiz kerak: fs: uch API shakli, process obyekti, Oqimlar, pipe va yo'naltirish, Asinxron iteratsiya.

1. Nega bu kerak?

zlib darsigacha valyuta bir martalik buyruqlar bilan ishladi: valyuta 100 usd — javob — dastur tugadi. Endi tasavvur qiling: Jasur aka bozorda turibdi va ketma-ket beshta summani hisoblamoqchi. Har safar valyuta ni qaytadan ishga tushirsa, har safar CBU'ga so'rov ketadi (yoki kesh o'qiladi) va har safar ~80 ms ishga tushish vaqti sarflanadi.

Qulayrog'i — interaktiv rejim: dastur bir marta ishga tushadi, kurslarni bir marta oladi, keyin valyuta> taklifini chiqarib, qator kutib turadi. Bu Python'ning >>> yoki node ning o'z REPL'iga o'xshaydi.

Ikkinchi vazifa ham qatorlar bilan bog'liq. 40 MB lik CSV faylni readFile bilan butunlay o'qib, split("\n") qilsangiz — butun fayl xotirada turadi. Qatorma-qator o'qisangiz — bir vaqtda faqat bitta qator. Ikkalasini ham readline qiladi.

O'xshatish: readline — kassadagi navbat boshqaruvchisi. Odamlar to'da bo'lib keladi (baytlar bo'lak-bo'lak keladi), u ularni bittalab kassaga o'tkazadi (bitta qator — bitta hodisa).

2. Oqimdan qatorlar: createInterface

2.1 Bo'lak va qator bir xil emas

Oqim (oqim (stream) — brauzerda ko'rgansiz) ma'lumotni bo'laklar bilan beradi. Bo'lak chegarasi qator chegarasiga to'g'ri kelmaydi: "lag'mon" so'zi ikki bo'lakka bo'linib kelishi mumkin. readline bo'laklarni yig'ib, faqat to'liq qatorlarni beradi.

Quyidagi misolda oqimni qo'lda yasaymiz: Readable.from([...]) — massivdagi har element bitta bo'lak. Stream'larni Stream nima darsida to'liq o'rganamiz; hozir u "bo'laklar beradigan manba" deb tushuning.

js
import { createInterface } from "node:readline/promises";
import { Readable } from "node:stream";

// 2 ta bo'lak: "lag'mon" ikkiga bo'lingan, Windows qator oxiri bor
const input = Readable.from(["osh\nlag'", "mon\r\nmanti"]);
const rl = createInterface({ input, crlfDelay: Infinity });

let n = 0;
for await (const line of rl) {
  n++;
  console.log(`${n}: ${JSON.stringify(line)}`);
}
console.log("tugadi");

Konsolda:

text
1: "osh"
2: "lag'mon"
3: "manti"
tugadi

Uchta narsaga qarang:

  • "lag'mon" bitta qator bo'lib keldi — bo'lak chegarasi ko'rinmaydi.
  • \r\n (Windows qator oxiri) qatorga kirmadi: "lag'mon", "lag'mon\r" emas.
  • Oxirgi "manti" dan keyin \n yo'q, lekin u ham qator sifatida keldi. Oqim tugagach, for await sikli o'zi to'xtadi.

crlfDelay: Infinity — \r va \n orasida qancha vaqt o'tsa ham, ularni bitta qator oxiri deb hisoblash. Sukut bo'yicha 100 ms. Fayl o'qiyotganda Node hujjati aynan Infinity ni tavsiya qiladi: katta faylda \r bir bo'lak oxirida, \n keyingisining boshida kelishi mumkin.

2.2 Ikki API: callback va promise

readline ning ikki kirish nuqtasi bor:

Modul Uslub Savol berish
node:readline hodisalar va callback (rl.on("line", …)) rl.question(matn, callback)
node:readline/promises await await rl.question(matn)

Ikkalasida ham createInterface bir xil, for await ikkalasida ishlaydi. Callback uslubini eski kodda ko'p uchratasiz:

js
import { createInterface } from "node:readline";
import { Readable } from "node:stream";

const input = Readable.from(["100 usd\n1 eur\n"]);
const rl = createInterface({ input });
rl.on("line", (line) => console.log("qator:", line));
rl.on("close", () => console.log("yopildi"));
console.log("tinglovchilar qo'yildi");

Konsolda:

text
tinglovchilar qo'yildi
qator: 100 usd
qator: 1 eur
yopildi

Birinchi bo'lib oxirgi qator chiqdi — "line" hodisalari keyinroq, oqim ma'lumot bergach keladi. rl.on(...) — bu hodisa tinglovchisi: brauzerdagi addEventListener ning Node'dagi o'xshashi. Uni EventEmitter asoslari darsida chuqur ko'ramiz. "close" — kirish oqimi tugaganda yoki rl.close() chaqirilganda.

Tekshirib ko'ring: Kirish Readable.from(["a\nb", "\nc\n"]) bo'lsa, for await nechta qator beradi va qaysilar?

Javob

Uchta: "a", "b", "c". "b" birinchi bo'lak oxirida, uning \n i keyingi bo'lak boshida — readline ularni birlashtiradi. Oxirgi \n dan keyin hech narsa yo'q, shuning uchun bo'sh to'rtinchi qator chiqmaydi.

3. Terminal: stdin, stdout va TTY

3.1 Uchta standart oqim

process obyekti darsida process.argv va process.env ni ko'rgansiz. Har jarayonda yana uchta oqim bor (Oqimlar, pipe va yo'naltirish darsidagi stdin, stdout, stderr — Node'da ular obyekt):

Obyekt Yo'nalish Odatda
process.stdin dasturga kiradi klaviatura yoki quvur
process.stdout dasturdan chiqadi natija
process.stderr dasturdan chiqadi xato va ogohlantirish

console.log — aslida process.stdout.write(matn + "\n"). valyuta natijani stdout'ga, "Ogohlantirish: offline rejim" ni stderr'ga yozadi — shuning uchun valyuta kurs > kurs.txt faylga faqat jadval tushadi.

3.2 TTY yoki quvur?

TTY (teletype — terminal qurilmasi) — odam bilan bevosita gaplashayotgan terminal oynasi. U klaviaturadan harf-harf oladi, rang va kursor harakatini tushunadi. Quvur (pipe) — boshqa dastur chiqishi: printf "100 usd\n" | valyuta i. Quvurda odam yo'q, faqat tayyor matn bor.

Node buni isTTY xususiyati bilan aytadi. Biz ikkala holatni ham ishga tushirdik (Node 24.21.0, Windows 11, Git Bash):

bash
echo x | node -p "process.stdin.isTTY"
node -p "process.stdout.isTTY" | cat
text
undefined
undefined

Birinchi buyruqda stdin — quvur, ikkinchisida stdout — quvur (| cat). Oddiy terminal oynasida ikkalasi true bo'ladi (Node hujjati bo'yicha). Bizning o'lchov muhitimizda haqiqiy terminal oynasi yo'q edi, shuning uchun true holatini o'zingiz tekshiring: node -p "process.stdin.isTTY".

E'tibor bering: quvurda false emas, undefined. Shuning uchun tekshiruv if (process.stdin.isTTY) yoki === true bilan yoziladi.

Nega bu muhim? Chunki yaxshi CLI ikkala holatda ham to'g'ri ishlaydi:

flowchart TD
  S["valyuta i ishga tushdi"] --> T{"chiqish TTY mi?"}
  T -->|ha| A["salomlashuv, valyuta> taklifi<br/>Tab, tarix"]
  T -->|yo'q| B["taklifsiz: har qator — buyruq"]
  A --> L["for await (qator of rl)"]
  B --> L
  L --> E{"xato qator bo'ldimi?"}
  E -->|TTY| K["davom etadi, exit 0"]
  E -->|quvur| X["oxirida exit 1"]

Diagrammada ikki yo'l bitta siklga qo'shiladi: farq faqat bezakda va exit kodida. Quvurga valyuta> taklifini yozish — natija fayliga axlat qo'shish degani. Quvurda xato bo'lsa exit 1 — skript buni bilsin (Exit code).

Tekshirib ko'ring: valyuta kurs | grep USD buyrug'ida valyuta uchun qaysi oqim TTY, qaysi biri emas?

Javob

stdout — quvur (grep ga ketadi), shuning uchun process.stdout.isTTY — undefined. stdin va stderr hali ham terminal — ular TTY. Shu sababli ogohlantirishlar (stderr) ekranda ko'rinadi, jadval esa grep ga ketadi.

4. Savol berish: rl.question

4.1 Oddiy savol-javob

readline/promises dagi rl.question(matn) — matnni chiqaradi va bitta qator javobni kutadi:

js
// savol.mjs
import { createInterface } from "node:readline/promises";
import { stdin as input, stdout as output } from "node:process";

const rl = createInterface({ input, output });
const name = await rl.question("Ismingiz? ");
const age = await rl.question("Yoshingiz? ");
console.log(`Salom, ${name}! Siz ${age} yoshdasiz.`);
rl.close();

Terminalda ishlatsangiz — hammasi kutilgandek: "Ismingiz?" chiqadi, siz yozasiz, Enter, keyingi savol. rl.close() muhim: usiz stdin ochiq qoladi va dastur terminalda tugamaydi (Node hujjati).

4.2 Biz topgan tuzoq: quvurda javob yo'qoladi

Endi xuddi shu faylga javoblarni quvur bilan beramiz — skriptlar va testlar aynan shunday qiladi. Biz ishga tushirdik (Node 24.21.0, Git Bash, Windows 11):

bash
printf "Ali\n25\n" | node savol.mjs
echo $?
text
Ismingiz? Yoshingiz? Warning: Detected unsettled top-level await at file:///C:/Users/…/savol.mjs:6
const age = await rl.question("Yoshingiz? ");
            ^

13

Tarjimasi: "Ogohlantirish: tugallanmagan top-level await aniqlandi". Ikkinchi savolga javob kelmadi, dastur exit kod 13 bilan tugadi. "25" qayerga ketdi?

Sababi — vaqt. Quvur ikkala qatorni bitta bo'lakda birdaniga beradi. readline "Ali" ni birinchi savolga beradi, "25" ni ham darhol "line" hodisasi sifatida chiqaradi. Lekin o'sha paytda ikkinchi savol hali berilmagan: await keyingi qatorga o'tguncha "25" o'tib ketdi. Keyin oqim tugadi, rl yopildi, ikkinchi await hech qachon javob olmaydi. Node bunday osilib qolgan modulni 13-kod bilan to'xtatadi. stdin o'rniga Readable.from bersangiz, ikkinchi question darhol ERR_USE_AFTER_CLOSE xatosini tashlaydi (readline was closed — "readline yopilgan").

Terminalda bu ko'rinmaydi: odam sekin yozadi, har qator alohida keladi. Shuning uchun tuzoq odatda faqat avtomatik test yoki skriptda chiqadi.

4.3 Tuzatish: iterator orqali so'rash

for await qatorlarni yo'qotmaydi, chunki u ichida navbat saqlaydi. Savollar uchun ham shu iteratorni qo'lda ishlatamiz: rl[Symbol.asyncIterator]() (Asinxron iteratsiya) va har savolda next():

js
import { createInterface } from "node:readline/promises";
import { Readable, PassThrough } from "node:stream";

// test uchun: kirish — "quvur", chiqish — xotiradagi oqim
const input = Readable.from(["Ali\n25\n"]);
const output = new PassThrough();
const rl = createInterface({ input, output });
const lines = rl[Symbol.asyncIterator]();

async function ask(text) {
  output.write(text);
  const { value, done } = await lines.next();
  return done ? "" : value;
}

const name = await ask("Ismingiz? ");
const age = await ask("Yoshingiz? ");
rl.close();
console.log(`Salom, ${name}! Siz ${age} yoshdasiz.`);

Konsolda:

text
Salom, Ali! Siz 25 yoshdasiz.

Ikkala javob ham joyida. Bu usul terminalda ham ishlaydi. done — oqim tugagan (Ctrl+D bosilgan yoki quvur tugagan) holat: javob o'rniga bo'sh satr qaytaramiz.

PassThrough — kirganini o'zgarishsiz chiqaradigan oqim. Testda process.stdout o'rniga uni beramiz va yozilganini o'zgaruvchiga yig'amiz. valyuta testlari aynan shunday qiladi (pastda).

Diqqat: rl.question ni for await (const qator of rl) bilan bitta rl da aralashtirmang. Ikkalasi bir xil qatorlarga da'vo qiladi va qaysi biri olishi vaqtga bog'liq bo'lib qoladi.

5. Interaktiv terminal imkoniyatlari

createInterface ga terminal: true (yoki output TTY bo'lsa — avtomatik) bo'lsa, readline odam uchun qulayliklar qo'shadi:

Sozlama yoki metod Nima qiladi
rl.setPrompt("valyuta> "), rl.prompt() taklif matnini chiqaradi
historySize: 100 ↑/↓ bilan oldingi buyruqlarni qaytarish
completer: (qator) => [variantlar, qism] Tab bosilganda to'ldirish
rl.on("SIGINT", …) Ctrl+C ni o'zingiz ushlash
readline.cursorTo, readline.clearLine kursorni ko'chirish, qatorni tozalash

Completer (to'ldiruvchi) — siz yozgan qatorni olib, ikki elementli massiv qaytaradigan funksiya: mos variantlar va to'ldirilayotgan qism. valyuta da u oxirgi so'zni katta harfga o'tkazib, valyuta kodlari ichidan qidiradi: 100 us + Tab → USD.

Ctrl+D — "kirish tugadi" (EOF) belgisi: for await sikli tugaydi. Ctrl+C — readline uni "SIGINT" hodisasiga aylantiradi; tinglovchi bo'lmasa, rl yopiladi. Signallar haqida process obyekti darsida gaplashgansiz.

cursorTo va clearLine "yuklanmoqda 45%" kabi bir joyda yangilanadigan qator uchun kerak. Ularni Interaktiv CLI darsida spinner bilan ishlatamiz.

Diqqat: Bu bo'limdagi TTY imkoniyatlari (taklif, Tab, tarix, Ctrl+C) bizning o'lchov muhitimizda sinalmagan: agentda haqiqiy terminal oynasi yo'q edi. Kod yo'li Node hujjatiga mos yozilgan; o'z terminalingizda albatta tekshiring.

6. Faylni qatorma-qator o'qish

Ikkinchi vazifa — katta fayl. createReadStream faylni bo'laklab o'qiydi, readline qatorlarga bo'ladi:

js
import { createReadStream } from "node:fs";
import { createInterface } from "node:readline";

const rl = createInterface({
  input: createReadStream("kurslar.csv"),
  crlfDelay: Infinity,
});
let count = 0;
for await (const line of rl) {
  if (line.startsWith("USD,")) count++;
}
console.log(`USD qatorlari: ${count}`);

Fayl qanchalik katta bo'lmasin, xotirada bir vaqtda faqat bitta bo'lak (sukut bo'yicha 64 KB) va joriy qator turadi. Kanon o'lchovi: 41 MB, 1 mln qatorli CSV — shu usulda eng ko'p 63 MB xotira, butun faylni o'qib split qilganda — 194 MB. Lekin stream usuli tezroq emas: 796 ms va 567 ms. Batafsil — Katta faylni stream bilan qayta ishlash darsida.

createReadStream ni fs chuqur darsida tilga olgandik. U ham oqim — keyingi darslarda uning ichini ochamiz.

7. CLI qadami: valyuta i — interaktiv rejim (holat 23)

7.1 Avval mantiqni ajratamiz

Interaktiv rejim valyuta 100 usd bilan aynan bir xil buyruqlarni bajarishi kerak. Mantiq cli.ts ichida edi. Uni interaktiv.ts dan chaqirsak, cli.ts → interaktiv.ts → cli.ts aylanma import paydo bo'lardi (CommonJS ichidan darsidagi circular mavzusi). Shuning uchun kanon mantiqni yangi faylga ko'chirdi:

  • src/buyruq.ts — bajar(sozlar, kontekst): kurs … yoki <miqdor> <KOD> [KOD] → natija matni. Kirish-chiqish yo'q, faqat hisob. FoydalanishXatosi va Kontekst turi ham shu yerda.
  • cli.ts — endi bitta qator: yoz(bajar(positionals, kontekst)).

Bu "funksional yadro" g'oyasi: hisob alohida, terminal alohida. Shunda bitta bajar ni ikki joydan chaqirish mumkin.

7.2 src/interaktiv.ts

Kanon faylining asosiy qismi (kod aynan, ba'zi qatorlar 70 belgidan uzun):

ts
import { createInterface } from "node:readline/promises";
import { FoydalanishXatosi, bajar, type Kontekst } from "./buyruq.ts";

export async function interaktiv(k: Kontekst, kirish: NodeJS.ReadableStream, chiqish: NodeJS.WritableStream & { isTTY?: boolean }): Promise<number> {
  const tty = chiqish.isTTY === true;
  const kodlar = ["UZS", ...k.kurslar.map((x) => x.kod)];
  const rl = createInterface({
    input: kirish,
    output: chiqish,
    terminal: tty,
    historySize: 100,
    completer: (qator: string): [string[], string] => {
      const oxirgi = qator.split(/\s+/).at(-1)?.toUpperCase() ?? "";
      const mos = kodlar.filter((kod) => kod.startsWith(oxirgi));
      return [mos.length ? mos : kodlar, oxirgi];
    },
  });
  // … yoz(), salomlashuv (faqat tty)
  rl.setPrompt("valyuta> ");
  if (tty) rl.prompt();
  let xatolar = 0;
  for await (const qator of rl) {
    const sozlar = qator.trim().split(/\s+/).filter(Boolean);
    const [birinchi] = sozlar;
    if (birinchi === "chiqish" || birinchi === "exit") break;
    if (birinchi === "yordam" || birinchi === "?") yoz(QISQA_YORDAM);
    else if (birinchi !== undefined) {
      try {
        yoz(bajar(sozlar, k));
      } catch (xato) {
        if (!(xato instanceof FoydalanishXatosi)) throw xato;
        xatolar++;
        yoz(`Xato: ${xato.message}`);
      }
    }
    if (tty) rl.prompt();
  }
  rl.close();
  // quvurda xato qator bo'lsa — skript buni exit kod orqali bilsin
  return !tty && xatolar > 0 ? 1 : 0;
}

Asosiy qarorlar:

  • kirish va chiqish parametr. Funksiya process.stdin ni o'zi olmaydi — testda boshqa oqim berish uchun.
  • for await, rl.question emas — «Biz topgan tuzoq» bo'limidagi sabab bilan, quvurdagi har qator saqlanadi.
  • Xato qator rejimni to'xtatmaydi — Xato: Noma'lum valyuta: xyz chiqadi va keyingi qator kutiladi. Kutilmagan xato (FoydalanishXatosi emas) esa yuqoriga uchadi — uni yashirmaymiz.
  • cli.ts da: if (birinchi === "interaktiv" || birinchi === "i") return await interaktiv(kontekst, process.stdin, process.stdout); va yordam matnida yangi qator.

7.3 Testlar

test/interaktiv.test.ts da uchta yangi test (jami 30). Ikkitasi funksiyani to'g'ridan chaqiradi — kirish Readable.from, chiqish PassThrough:

ts
async function yurgiz(qatorlar: string[]): Promise<{ kod: number; chiqish: string }> {
  const chiqish = new PassThrough();
  let matn = "";
  chiqish.on("data", (b: Buffer) => (matn += b.toString()));
  const kod = await interaktiv(k, Readable.from(qatorlar.map((q) => q + "\n")), chiqish);
  return { kod, chiqish: matn };
}

test("quvur rejimi: har qator — buyruq, taklif (prompt) yo'q", async () => {
  const r = await yurgiz(["100 usd", "", "1 eur usd", "chiqish", "100 usd"]);
  assert.equal(r.kod, 0);
  assert.equal(r.chiqish, "100.00 USD = 1 190 315.00 UZS\n1.00 EUR = 1.12 USD\n");
});

Bo'sh qator e'tiborsiz qoldi, chiqish dan keyingi "100 usd" bajarilmadi. Uchinchi test valyuta i ni haqiqiy jarayon sifatida ishga tushirib, stdin'ga "10 usd\n" yozadi — execFile bilan. Jarayonni koddan ishga tushirishni ikki darsdan keyin, child_process darsida ko'rasiz.

7.4 Haqiqiy chiqish

Kanon quvur orqali ishga tushirdi (natijalar/23-interaktiv.txt):

bash
printf "100 usd\nkurs gbp jpy\n1 xyz\nyordam\nchiqish\n" | valyuta i --offline
text
Ogohlantirish: offline rejim — saqlangan namuna kurslar (fixtures/) ishlatildi
100.00 USD = 1 190 315.00 UZS
2026-10-23 holatiga (fixture)
GBP    1    15 916.89  -112.11  Angliya funt sterlingi
JPY    1        75.81     0.03  Yaponiya iyenasi
Xato: Noma'lum valyuta: xyz
Buyruqlar: "100 usd", "100 usd eur", "kurs", "kurs gbp jpy", "yordam", "chiqish" (yoki Ctrl+D). Tab — valyuta kodini to'ldiradi.
[exit 1]

valyuta> taklifi va salomlashuv yo'q — chiqish TTY emas. 1 xyz xatosidan keyin ham yordam bajarildi. Oxirida exit 1 — chunki bitta xato qator bo'ldi.

Tekshiruv: tip va lint 0, testlar 30/30, paket 44 fayl, 20.2 kB, sovuq start 79 ms. TTY rejimi (taklif, Tab, tarix) — kanon muhitida sinalmagan; o'z terminalingizda valyuta i ni ishga tushirib ko'ring.

Commit: Interaktiv rejim: valyuta i — readline, Tab bilan valyuta kodi, quvurda ham ishlaydi

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Dastur tugamaydi, kursor miltillaydi rl.close() chaqirilmagan, stdin ochiq Ish tugagach rl.close()
Warning: Detected unsettled top-level await, exit 13 Quvurda rl.question javobni o'tkazib yubordi for await yoki iteratorning next()
Error [ERR_USE_AFTER_CLOSE]: readline was closed Oqim tugagach yana question done ni tekshiring, close dan keyin so'ramang
Qator oxirida ko'rinmas \r ("usd\r") Windows \r\n, crlfDelay qisqa yoki qo'lda split("\n") crlfDelay: Infinity; qo'lda bo'lsangiz /\r?\n/
Natija faylida valyuta> va ranglar Taklif quvurga yozilgan isTTY === true bo'lgandagina
Test osilib qoladi Testda process.stdin o'qilyapti Kirish/chiqishni parametr qiling

9. Mashqlar

Mashqlar kurs/mashqlar/24/23-readline/ da. Bloklarni o'z terminalingizda node fayl.mjs bilan ishga tushiring.

1-mashq (oson): bo'shliqlarni to'ldiring

  • Oqimni qatorlarga bo'ladigan funksiya —
  • Windows qator oxirini to'g'ri birlashtirish sozlamasi — crlfDelay:
  • Quvurda process.stdin.isTTY qiymati —
  • await bilan savol berish uchun modul — node:readline/
  • Kirishni tugatadigan tugmalar — Ctrl+
Yechim
  • createInterface — readline ning asosiy funksiyasi.
  • Infinity — \r va \n qancha kechiksa ham bitta qator oxiri.
  • undefined — false emas; shuning uchun === true bilan tekshiramiz.
  • promises — node:readline/promises.
  • D — Ctrl+D EOF yuboradi (Windows cmd da Ctrl+Z, keyin Enter).

2-mashq (o'rta): savdo kundaligi

Readable.from bilan Jasur akaning kunlik savdosi berilgan: har qator taom,narx. readline va for await bilan har taom bo'yicha jami summani hisoblang. Bo'sh qatorlarni o'tkazib yuboring. Kirish: ["osh,35000\nmanti,30", "000\n\nosh,35000\r\nko'k choy,5000\n"].

Yechim
js
import { createInterface } from "node:readline/promises";
import { Readable } from "node:stream";

const input = Readable.from([
  "osh,35000\nmanti,30",
  "000\n\nosh,35000\r\nko'k choy,5000\n",
]);
const rl = createInterface({ input, crlfDelay: Infinity });
const totals = new Map();
for await (const line of rl) {
  if (!line.trim()) continue;
  const [dish, price] = line.split(",");
  totals.set(dish, (totals.get(dish) ?? 0) + Number(price));
}
for (const [dish, sum] of totals) console.log(`${dish}: ${sum}`);

Konsolda:

text
osh: 70000
manti: 30000
ko'k choy: 5000

"30" va "000" ikki bo'lakda keldi — readline ularni birlashtirdi. \r\n dan keyingi "osh" toza keldi, shuning uchun Map da ikkita "osh" emas, bitta.

3-mashq (qiyin): quvurga chidamli test-viktorina

Uch savolli viktorina yozing: savollar massivi { q, a }, javob katta-kichik harfga qaramay solishtiriladi. Kirish va chiqish — parametr (quiz(input, output)), natija — to'g'ri javoblar soni. rl.question ishlatmang (nega — «Biz topgan tuzoq» bo'limida). Javoblar quvurdagidek bitta bo'lakda keladi: "Toshkent\n5\nNODE\n". Javob kelmasa (oqim tugasa) — qolgan savollar noto'g'ri hisoblanadi.

Yechim
js
import { createInterface } from "node:readline/promises";
import { Readable, PassThrough } from "node:stream";

const QUESTIONS = [
  { q: "O'zbekiston poytaxti? ", a: "toshkent" },
  { q: "2 + 3 = ? ", a: "5" },
  { q: "JS'ni serverda bajaradigan runtime? ", a: "node" },
];

async function quiz(input, output) {
  const rl = createInterface({ input, output, terminal: false });
  const lines = rl[Symbol.asyncIterator]();
  let correct = 0;
  for (const { q, a } of QUESTIONS) {
    output.write(q);
    const { value, done } = await lines.next();
    if (done) break;
    if (value.trim().toLowerCase() === a) correct++;
  }
  rl.close();
  return correct;
}

const out = new PassThrough();
let screen = "";
out.on("data", (b) => (screen += b));
const full = await quiz(Readable.from(["Toshkent\n5\nNODE\n"]), out);
const halfInput = Readable.from(["toshkent\n"]);
const half = await quiz(halfInput, new PassThrough());
console.log(`to'liq: ${full}/3, yarim: ${half}/3`);
console.log(screen.split("? ").length - 1, "ta savol chiqdi");

Konsolda:

text
to'liq: 3/3, yarim: 1/3
3 ta savol chiqdi

terminal: false — chiqish PassThrough bo'lgani uchun aniq aytdik (TTY emas). Ikkinchi chaqiruvda oqim birinchi javobdan keyin tugadi: done — true, sikl to'xtadi, dastur osilmadi.

4-mashq: CLI qadami — interaktiv rejim

O'z valyuta ingizda «CLI qadami» bo'limidagi qadamni bajaring: buyruq.ts ga mantiqni ko'chiring, interaktiv.ts ni yozing, cli.ts ga i buyrug'ini qo'shing, uchta testni yozing. Keyin tekshiring:

bash
printf "100 usd\n1 xyz\nchiqish\n" | node src/bin.ts i --offline
echo "exit $?"
npm run tip && npm run lint && npm test
Yechim

Kutilgan: 100.00 USD = 1 190 315.00 UZS, Xato: Noma'lum valyuta: xyz, exit 1; testlar 30/30. Keyin terminalda node src/bin.ts i — valyuta> taklifi chiqishi, 100 us + Tab USD ga to'ldirishi, ↑ oldingi buyruqni qaytarishi kerak. Hammasi o'tsa — yuqoridagi commit xabari bilan commit qiling.

10. Real ishda

  • REPL'lar — node, psql, redis-cli, Prisma Studio'ning terminal qismlari: hammasi readline yoki uning ustidagi kutubxonalar.
  • Log tahlili — server loglari gigabaytlab bo'ladi. createReadStream + readline + for await — Node'da eng oddiy "qatorma-qator" ishlov.
  • TTY va quvur farqi — professional CLI'lar (git, npm) TTY'da rang va progress chiqaradi, quvurda — toza matn. isTTY tekshiruvi har yaxshi CLI'da bor.
  • Interaktiv savollar — npm init, create-vite savollari. Ular @clack/prompts yoki inquirer kabi kutubxonalarda; Interaktiv CLI darsida ularni o'lchab taqqoslaymiz.
  • Intervyuda: "Katta faylni qanday o'qiysiz?" — readFile emas, stream + readline; "CLI'ni skriptdan qanday test qilasiz?" — kirish/chiqishni parametr qilish.

Xulosa

  • createInterface({ input }) oqimni qatorlarga bo'ladi; bo'lak chegarasi muhim emas, crlfDelay: Infinity — \r\n uchun.
  • for await (const qator of rl) — qatorlarni o'qishning eng ishonchli yo'li; oqim tugasa sikl o'zi tugaydi.
  • rl.question terminalda qulay, lekin quvurda javobni yo'qotadi (biz o'lchadik: exit 13) — iteratorning next() bilan yozing.
  • process.stdin.isTTY / process.stdout.isTTY: terminalda true, quvurda undefined.
  • TTY'da taklif, Tab (completer), tarix; quvurda — toza matn va xato bo'lsa exit 1.

Keyingi dars: timers va timers/promises chuqur — await qilinadigan kutish, AbortSignal.timeout va valyuta ga qayta urinish.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Node.js readline: terminalda savol-javob va faylni qatorma-qator o'qish — IlmHamroh