IlmHamroh
JavaScript Full-stack/24-qism. Node.js33/55-dars17 daqiqa
Mundarija (27)

Node.js Transform stream yozish: o'z Readable va Transform oqimingiz

Qisqacha: O'z oqimingizni yasashning uch yo'li bor. Readable — read() ichida this.push(bo'lak) qilasiz, oxirida push(null). Transform — transform(bo'lak, kodlash, tayyor) har bo'lakni o'zgartiradi, flush(tayyor) esa oxirida qolganini chiqaradi. Eng qisqa yo'l — Readable.from(generator) va pipeline ichidagi async generator. Bayt o'rniga obyekt uzatish uchun objectMode: true kerak.

Bu darsda

  • new Readable({ read }) bilan ma'lumot manbai yasay olasiz.
  • new Transform({ transform, flush }) bilan bo'laklarni yo'lda o'zgartira olasiz.
  • Qator o'rtasida uzilgan bo'laklarni flush bilan to'g'ri yig'asiz.
  • objectMode qachon kerakligini va uning highWaterMark farqini tushuntira olasiz.
  • Transform klass va async generator orasidan asos bilan tanlaysiz.

Oldin bilishingiz kerak: Stream nima: to'rt turi va rejimlari, pipeline, finished va stream xatolari, Asinxron iteratsiya, Streams API.

1. Nega bu kerak?

Oldingi darsda tayyor oqimlarni pipeline bilan uladik: createReadStream, createGunzip, createWriteStream. Ular Node'ning o'zida bor. Lekin ishning eng qiziq qismi ko'pincha o'rtada bo'ladi: CSV qatorini obyektga aylantirish, keraksiz valyutalarni tashlash, natijani JSON'ga o'girish. Buni hech bir tayyor modul siz uchun qilmaydi.

valyuta CLI'da aynan shunday zanjir bor. valyuta tarix EUR --dan 2026-09-20 -f json kunma-kun kurslarni oladi va ularni matnga aylantirib, faylga yozadi. Kurslar — obyektlar, fayl esa baytlar kutadi. Orada kimdir obyektni matnga o'girishi kerak.

Hayotiy o'xshatish: non zavodi konveyeri. Bir tomonda un qopi (manba), oxirida qadoqlangan non (yozish). O'rtada stanoklar turadi: xamir qorish, kesish, pishirish. Har stanok bitta ishni qiladi va tayyorini keyingisiga uzatadi. Bugun siz o'z stanogingizni yasaysiz va uni konveyerga qo'yasiz.

Streams API darsida brauzerda o'z TransformStream ingizni yozgan edingiz — qatorlarga bo'luvchi. G'oya o'sha. Faqat Node'ning o'z oqimlari (node:stream) biroz boshqacha yoziladi va ular Node ekotizimida hamma joyda ishlatiladi.

2. O'z Readable oqimingiz

2.1 read() va push()

Readable — ma'lumot manbai. O'z manbangizni yasash uchun read() funksiyasini berasiz. Node uni "menga yana ma'lumot kerak" degan paytda chaqiradi. Siz this.push(bo'lak) bilan bo'lak berasiz. Ma'lumot tugaganda — this.push(null):

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

let kun = 20;
const kunlar = new Readable({
  read() {
    if (kun > 23) {
      this.push(null); // oqim tugadi
      return;
    }
    this.push(`2026-10-${kun}\n`);
    kun++;
  },
});
kunlar.on("data", (b) => {
  console.log("bo'lak:", JSON.stringify(String(b)));
});
kunlar.on("end", () => console.log("tugadi"));

Konsolda:

text
bo'lak: "2026-10-20\n"
bo'lak: "2026-10-21\n"
bo'lak: "2026-10-22\n"
bo'lak: "2026-10-23\n"
tugadi

Ikki narsaga e'tibor bering. Birinchi: biz read() ni o'zimiz chaqirmadik — uni Node chaqirdi, chunki data tinglovchisi ma'lumot so'radi. Ikkinchi: String(b) kerak bo'ldi. Oddiy oqim satrni Buffer ga aylantirib uzatadi (Buffer darsini eslang).

push() ning qaytargan qiymati ham bor. U false qaytarsa — ichki bufer to'ldi, ko'proq bermang. Bu backpressure darsidagi write() ning teskari tomoni. read() ichida bitta bo'lak berib qaytsangiz, bu qoida o'z-o'zidan bajariladi: Node keyingi bo'lakni o'zi so'raydi.

2.2 Faqat satr yoki bayt

Oddiy oqimga son bersangiz nima bo'ladi?

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

const r = new Readable({ read() {} });
r.on("error", (x) => console.log(x.code));
r.push(12010);

Konsolda:

text
ERR_INVALID_ARG_TYPE

To'liq xabar shunday:

text
The "chunk" argument must be of type string or an instance of Buffer, TypedArray, or DataView. Received type number (12010)

Tarjimasi: "chunk (bo'lak) argumenti satr yoki Buffer, TypedArray, DataView nusxasi bo'lishi kerak. Son (12010) berildi". Oqim sukut bo'yicha baytlar uchun mo'ljallangan — u fayl, tarmoq va siqish bilan ishlaydi. Son, obyekt, massiv uzatish uchun maxsus rejim kerak.

2.3 objectMode: baytlar o'rniga obyektlar

objectMode: true — oqimga "har bo'lak — bitta JS qiymati" deb aytish. Endi push({ kod: "USD" }) ham, push(12010) ham mumkin. Faqat null mumkin emas — u "oqim tugadi" degan belgi.

Bu rejimda highWaterMark (bufer chegarasi) ma'nosi ham o'zgaradi:

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

const baytli = new Readable({ read() {} });
const obyektli = new Readable({ objectMode: true, read() {} });
console.log("bayt oqimi:", baytli.readableHighWaterMark);
console.log("obyekt oqimi:", obyektli.readableHighWaterMark);

Konsolda:

text
bayt oqimi: 16384
obyekt oqimi: 16

Bayt oqimida bufer 16 384 bayt (16 KB) — bu Windows'dagi sukut; Linux/macOS'da 65 536 (Stream nima darsidagi jadval). Obyekt oqimida — 16 ta obyekt, ularning hajmidan qat'i nazar. Har obyekt 1 MB bo'lsa, buferda 16 MB yotadi. Katta obyektlar bilan ishlasangiz, highWaterMark ni kichraytiring.

Tekshirib ko'ring: read() ichida this.push(null) ni unutsangiz nima bo'ladi?

Javob

Oqim hech qachon end hodisasini chiqarmaydi. for await yoki pipeline uni o'qiyotgan bo'lsa, ular "yana ma'lumot keladi" deb abadiy kutadi — xuddi oldingi darsdagi .pipe() tuzog'i kabi. push(null) — "tugadi" degan yagona signal.

3. Readable.from: tayyor manbadan oqim

new Readable({ read }) ko'p hollarda ortiqcha. Ma'lumot massivda yoki generatorda bo'lsa, Readable.from uni bitta qatorda oqimga aylantiradi:

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

for await (const b of Readable.from(["osh", "manti"])) {
  console.log("massiv:", b);
}
for await (const b of Readable.from("salom")) console.log("satr:", b);
const r = Readable.from([1, 2]);
console.log(r.readableObjectMode, r.readableHighWaterMark);

Konsolda:

text
massiv: osh
massiv: manti
satr: salom
true 1

Uchta tuzoqqa qarang:

  1. Readable.from sukut bo'yicha objectMode da ishlaydi (true). Massivdagi har element — alohida bo'lak, Buffer emas.
  2. Satr bersangiz, u harflarga bo'linmaydi: "salom" — bitta bo'lak. (Satr ham iterable, lekin Readable.from uni ataylab butun uzatadi.)
  3. Bu yerda highWaterMark — 1. Generator bitta qiymatni oldindan tayyorlaydi, keyingisini so'ralganda.

Eng kuchli varianti — async generator dan oqim. Asinxron iteratsiya darsida yozgan async function* bevosita oqimga aylanadi:

js
import { Readable } from "node:stream";
import { setTimeout as kut } from "node:timers/promises";

async function* kurslar() {
  for (const [kod, kurs] of [["USD", 12010], ["EUR", 13437]]) {
    await kut(10); // tarmoq so'rovi o'rnida
    yield { kod, kurs };
  }
}
for await (const k of Readable.from(kurslar())) console.log(k);

Konsolda:

text
{ kod: 'USD', kurs: 12010 }
{ kod: 'EUR', kurs: 13437 }

Generator qachon chaqirilishini oqim boshqaradi. Qabul qiluvchi sekin bo'lsa, generator yield da to'xtab turadi — backpressure o'z-o'zidan ishlaydi. Kanonda valyuta tarix aynan shunday quriladi: tarixOqimi — kunma-kun CBU'dan so'raydigan async generator.

4. Transform: yo'lda o'zgartirish

4.1 transform funksiyasi

Transform — ham yoziladigan, ham o'qiladigan oqim: bir tomondan kiradi, o'zgarib, ikkinchi tomondan chiqadi. Yasash uchun transform(bolak, kodlash, tayyor) funksiyasini berasiz:

  • bolak — kirgan bo'lak;
  • kodlash — matn kodlashi (masalan "utf8"); ko'pincha kerak emas, shuning uchun _kod deb nomlaymiz;
  • tayyor(xato, natija) — "bu bo'lak bilan ishim tugadi" degan callback.

tayyor ni chaqirishning uch shakli bor:

Chaqiruv Ma'nosi
tayyor(null, natija) bitta natija chiqar va keyingisini ber
tayyor() bu bo'lakdan hech narsa chiqmaydi (filtr)
tayyor(new Error(...)) xato — pipeline hammasini yopadi

Bitta bo'lakdan bir nechta natija chiqarish kerak bo'lsa, this.push(...) ni xohlagancha chaqirasiz, oxirida tayyor(). Eng oddiy misol — sonni ikkiga ko'paytirish:

js
import { Transform } from "node:stream";

const ikkilantir = new Transform({
  objectMode: true,
  transform(son, _kod, tayyor) { tayyor(null, son * 2); },
});
ikkilantir.on("data", (x) => console.log("chiqdi:", x));
ikkilantir.write(5);
ikkilantir.write(7);
ikkilantir.end();

Konsolda:

text
chiqdi: 10
chiqdi: 14

4.2 Qator o'rtasida uzilgan bo'lak va flush

Diskdan yoki tarmoqdan kelgan bo'laklar qator chegarasini hurmat qilmaydi. 64 KB bo'lak qatorning o'rtasida uzilishi mumkin. Qatorlarga bo'luvchi Transform chala qismni qoldiq sifatida saqlab, keyingi bo'lakka qo'shishi kerak. Avval algoritmning o'zini oddiy JS'da qadamma-qadam ko'ramiz:

Oxirgi qadamga qarang. Fayl \n bilan tugamasa, oxirgi qator qoldiqda qolib ketadi. Transform'da buning uchun alohida joy bor — flush(tayyor). U kirish tugagandan keyin, oqim yopilishidan oldin bir marta chaqiriladi. Ikkala variantni solishtiramiz (fayl \n siz tugaydi):

js
import { Readable, Transform } from "node:stream";

// bo'laklar qator o'rtasida uziladi — xuddi diskdan o'qilgandek
const bolaklar = ["sana,ko", "d,kurs\n2026-10-22,U",
  "SD,12010\n2026-10-23,USD,12030"];

function qatorAjratuvchi(flushBilan) {
  let qoldiq = "";
  return new Transform({
    readableObjectMode: true,
    transform(bolak, _kod, tayyor) {
      const qismlar = (qoldiq + bolak).split("\n");
      qoldiq = qismlar.pop(); // oxirgisi chala bo'lishi mumkin
      for (const q of qismlar) this.push(q);
      tayyor();
    },
    flush: flushBilan
      ? function (tayyor) { if (qoldiq) this.push(qoldiq); tayyor(); }
      : undefined,
  });
}
for (const f of [false, true]) {
  const qatorlar = [];
  const oqim = Readable.from(bolaklar).pipe(qatorAjratuvchi(f));
  for await (const q of oqim) qatorlar.push(q);
  console.log(f ? "flush bilan:" : "flush'siz:", qatorlar.length);
}

Konsolda:

text
flush'siz: 2
flush bilan: 3

flush siz oxirgi kurs (2026-10-23,USD,12030) jim yo'qoldi — xato ham, ogohlantirish ham yo'q. Bu eng xavfli xato turi: natija to'g'riga o'xshaydi, lekin bitta qator kam.

Yana ikki yangi narsa bor. readableObjectMode: true — faqat chiqish tomoni obyekt rejimida: kirishga baytlar keladi, chiqishga satrlar (qatorlar) ketadi. Uning jufti — writableObjectMode. flush esa function bilan yozildi, strelka funksiya bilan emas: ichida this.push kerak, strelka funksiyada esa this oqimga bog'lanmaydi.

Maslahat: Node'da tayyor qatorlarga bo'luvchi bor — node:readline dagi createInterface (readline va terminal). Kanondagi valyuta tahlil aynan shuni ishlatadi. O'zingiz yozish — flush ni tushunish uchun mashq; real kodda tayyor, sinalgan vositani oling.

Tekshirib ko'ring: flush qachon chaqiriladi: har bo'lakdan keyinmi yoki bir martami?

Javob

Bir marta — kirish tomoni tugaganda (end() chaqirilganda yoki manba tugaganda), chiqish tomoni yopilishidan oldin. Shuning uchun u "oxirida qolgan narsani chiqarish" uchun ishlatiladi: chala qator, JSON massivning yopuvchi ], siqish algoritmining oxirgi baytlari. createGzip ham ichkarida xuddi shunday qiladi.

4.3 Klass bilan yozish

Murakkabroq Transform'ni klass sifatida yozish qulay: holat (state) xususiyatlarda turadi, metodlar nomlangan. Bunda funksiya nomlari pastki chiziq bilan boshlanadi — _transform va _flush. Ularni Node chaqiradi, siz emas:

js
import { Readable, Transform } from "node:stream";
import { pipeline } from "node:stream/promises";

class CsvObyekt extends Transform {
  #sarlavha = null;
  constructor() {
    super({ objectMode: true });
  }
  _transform(qator, _kod, tayyor) {
    const maydonlar = qator.split(",");
    if (this.#sarlavha === null) {
      this.#sarlavha = maydonlar;
      return tayyor();
    }
    const yozuv = {};
    this.#sarlavha.forEach((nom, i) => {
      yozuv[nom] = maydonlar[i];
    });
    tayyor(null, yozuv);
  }
}
const qatorlar = ["sana,kod,kurs", "2026-10-22,USD,12010",
  "2026-10-22,EUR,13437"];
await pipeline(Readable.from(qatorlar), new CsvObyekt(),
  async function* (oqim) {
    for await (const y of oqim) console.log(y);
  });

Konsolda:

text
{ sana: '2026-10-22', kod: 'USD', kurs: '12010' }
{ sana: '2026-10-22', kod: 'EUR', kurs: '13437' }

Birinchi qator — sarlavha: u saqlanadi va chiqarilmaydi (tayyor() natijasiz). Qolganlari sarlavha nomlari bilan obyektga aylanadi. #sarlavha — klassning yopiq maydoni, uni tashqaridan hech kim o'zgartira olmaydi.

Bu split(",") qo'shtirnoq ichidagi vergulni bilmaydi ("AQSH dollari, naqd"). Kanon buning uchun RFC 4180 bo'yicha csvniAjrat funksiyasini yozgan — uni keyingi darsda ko'ramiz.

5. Butun konveyer

Endi hammasini bitta zanjirga yig'amiz: obyektlar → JSON matn. Bu yerda flush boshqa ish qiladi — massivni yopadi. Diagrammada har bosqichda nima oqayotganiga qarang:

flowchart LR
  A["Readable.from<br/>obyektlar"] -->|"obyekt"| B["jsonMassiv"]
  B -->|"matn bo'laklari"| C["yozish / ekran"]
  B -. "flush: ]" .-> C
js
import { Readable, Transform } from "node:stream";
import { pipeline } from "node:stream/promises";

function jsonMassiv() {
  let birinchi = true;
  return new Transform({
    writableObjectMode: true,
    transform(yozuv, _kod, tayyor) {
      const boshi = birinchi ? "[\n  " : ",\n  ";
      birinchi = false;
      tayyor(null, boshi + JSON.stringify(yozuv));
    },
    flush(tayyor) {
      tayyor(null, birinchi ? "[]\n" : "\n]\n");
    },
  });
}
const yozuvlar = [
  { kod: "USD", kurs: 12010 },
  { kod: "EUR", kurs: 13437 },
];
let matn = "";
const manba = Readable.from(yozuvlar);
await pipeline(manba, jsonMassiv(), async (oqim) => {
  for await (const b of oqim) matn += b;
});
console.log(matn.trimEnd());
console.log(JSON.parse(matn).length);

Konsolda:

text
[
  {"kod":"USD","kurs":12010},
  {"kod":"EUR","kurs":13437}
]
2

Nega JSON.stringify(yozuvlar) qilib qo'ya qolmadik? Chunki u butun massiv xotirada bo'lishini talab qiladi. Bu Transform esa million yozuvni ham bittalab o'tkazadi: xotirada faqat joriy yozuv turadi. flush dagi birinchi ? "[]\n" — oqim bo'sh bo'lsa ham yaroqli JSON chiqishi uchun. Bo'sh kunlar oralig'i so'ralsa, foydalanuvchi buzuq [ emas, [] oladi.

pipeline ning oxirgi bo'g'ini — oddiy async funksiya (generator emas). Bunday funksiya oqimni o'qib tugatadi va "qabul qiluvchi" o'rnida turadi.

6. Klass, funksiya yoki generator?

Bir xil ishni uch xil yozish mumkin. Yuqoridagi CsvObyekt ni async generator bilan yozsak:

js
async function* csvObyekt(qatorlar) {
  let sarlavha = null;
  for await (const qator of qatorlar) {
    const maydonlar = qator.split(",");
    if (sarlavha === null) { sarlavha = maydonlar; continue; }
    const juftlar = sarlavha.map((n, i) => [n, maydonlar[i]]);
    yield Object.fromEntries(juftlar);
  }
}

flush kerak emas: sikldan keyingi kod — o'sha flush. tayyor callback'i ham yo'q: yield — natija, throw — xato, continue — filtr.

Usul Qachon tanlanadi
async generator ko'p holat; qisqa, o'qish oson, test qilish oson
new Transform({...}) .pipe() kerak yoki boshqa API aynan oqim kutadi
class extends Transform kutubxona yozasiz, qayta ishlatiladigan sozlamali oqim

Generatorning bitta cheklovi bor: u pipeline ichida yoki Readable.from orqali ishlaydi. Funksiyadan oqim qaytarish kerak bo'lsa (masalan, faylniOch kabi), Transform kerak bo'ladi — chunki generator .pipe() ga ulanmaydi.

Kanon qaysi yo'lni tanladi? valyuta da bitta ham class extends Transform yo'q. Hamma oraliq ish — async generatorlar: tarixOqimi (kurslar), matnga (matn bo'laklari). Ular Readable.from(...) bilan oqimga aylanib, pipeline ga kiradi. Sabab — TypeScript tiplari va testlar: generatorni oqimsiz ham, oddiy massiv bilan sinash mumkin.

7. Ko'p uchraydigan xatolar

7.1 tayyor() ni chaqirmaslik

js
// ❌ birinchi bo'lakdan keyin oqim to'xtab qoladi
transform(bolak, _kod, tayyor) {
  this.push(bolak);
}

Node keyingi bo'lakni bermaydi — tayyor signalini kutyapti. Xato yo'q, natija yo'q. Har yo'lda aynan bir marta tayyor() yoki tayyor(null, natija).

7.2 tayyor ni ikki marta chaqirish

js
// ❌ filtr shartida return unutilgan
if (yozuv.kod !== "USD") tayyor();
tayyor(null, yozuv);

Natija: Error [ERR_MULTIPLE_CALLBACK]: Callback called multiple times. return tayyor(); — qaytish bilan.

7.3 objectMode siz obyekt

tayyor(null, { kurs: 1 }) oddiy Transform'da ERR_INVALID_ARG_TYPE beradi — «Faqat satr yoki bayt» bo'limidagi xato. objectMode: true yoki chiqish tomonigagina — readableObjectMode: true.

7.4 flush ni unutish

Oxirgi chala qator, JSON'ning yopuvchi ] — jim yo'qoladi. "Transform'da qoldiq bormi?" degan savol — har code review'da.

7.5 Strelka funksiyada this.push

js
// ❌ this — Transform emas
flush: (tayyor) => { this.push(qoldiq); tayyor(); },

Strelka funksiyaning this i tashqi kontekstdan olinadi. ESM modulda u undefined — TypeError: Cannot read properties of undefined (reading 'push'). flush(tayyor) { ... } yoki function (tayyor) { ... }. Yoki natijani tayyor(null, qoldiq) bilan bering — this umuman kerak bo'lmaydi.

8. Mashqlar

1-mashq (oson): Sanovchi Readable

Quyidagi oqim nima chiqaradi? Avval o'ylang, keyin o'z kompyuteringizda node bilan tekshiring.

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

let qolgan = 3;
const sanoq = new Readable({
  objectMode: true,
  read() {
    this.push(qolgan > 0 ? qolgan-- : null);
  },
});
for await (const son of sanoq) console.log(son);
console.log("start!");

Konsolda:

text
3
2
1
start!
Yechim

qolgan-- avval qiymatni beradi, keyin kamaytiradi: 3, 2, 1. Qiymat 0 bo'lganda push(null) — oqim tugaydi va for await dan chiqiladi. objectMode bo'lmasa, push(3) son bo'lgani uchun ERR_INVALID_ARG_TYPE berardi.

2-mashq (o'rta): Valyuta filtri

faqat(kod) funksiyasini yozing: u objectMode Transform qaytarsin va faqat yozuv.kod === kod bo'lgan yozuvlarni o'tkazsin. Kod katta-kichik harfga sezgir bo'lmasin ("usd" ham ishlasin). Sinov: Readable.from([{kod:"USD"},{kod:"EUR"},{kod:"USD"}]) → faqat("usd") → nechta yozuv o'tdi? Eslatma: filtrda tayyor() natijasiz chaqiriladi («transform funksiyasi» bo'limidagi jadval).

Yechim
js
import { Readable, Transform } from "node:stream";
import { pipeline } from "node:stream/promises";

function faqat(kod) {
  const kerak = kod.toUpperCase();
  return new Transform({
    objectMode: true,
    transform(yozuv, _kod, tayyor) {
      if (yozuv.kod !== kerak) return tayyor();
      tayyor(null, yozuv);
    },
  });
}
let soni = 0;
await pipeline(
  Readable.from([{ kod: "USD" }, { kod: "EUR" }, { kod: "USD" }]),
  faqat("usd"),
  async function (oqim) { for await (const _ of oqim) soni++; },
);
console.log("o'tdi:", soni);

Konsolda:

text
o'tdi: 2

return tayyor() — qaytish bilan: aks holda keyingi qatordagi tayyor(null, yozuv) ham chaqirilib, ERR_MULTIPLE_CALLBACK bo'lardi.

3-mashq (qiyin): Qatorlar sanog'i bilan CSV

qatorRaqami() Transform'ini yozing. U matn bo'laklarini olsin (qator o'rtasida uzilgan bo'lishi mumkin) va har qatorni "1: sana,kod" ko'rinishida chiqarsin. Oxirgi qator \n siz tugasa ham yo'qolmasin, bo'sh qatorlar o'tkazib yuborilsin. Sinov bo'laklari: ["sana,k", "od\n\n2026-10-23,U", "SD"]. Eslatma: flush va qoldiq — «Qator o'rtasida uzilgan bo'lak va flush» bo'limida.

Yechim
js
import { Readable, Transform } from "node:stream";
import { pipeline } from "node:stream/promises";

function qatorRaqami() {
  let qoldiq = "";
  let raqam = 0;
  const chiqar = (oqim, q) => {
    if (q !== "") oqim.push(`${++raqam}: ${q}`);
  };
  return new Transform({
    readableObjectMode: true,
    transform(bolak, _kod, tayyor) {
      const qismlar = (qoldiq + bolak).split("\n");
      qoldiq = qismlar.pop();
      for (const q of qismlar) chiqar(this, q);
      tayyor();
    },
    flush(tayyor) {
      chiqar(this, qoldiq);
      tayyor();
    },
  });
}
await pipeline(
  Readable.from(["sana,k", "od\n\n2026-10-23,U", "SD"]),
  qatorRaqami(),
  async function (oqim) {
    for await (const q of oqim) console.log(q);
  },
);

Konsolda:

text
1: sana,kod
2: 2026-10-23,USD

Bo'sh qator (\n\n orasidagi) q !== "" sharti bilan tashlandi, lekin raqam olmadi. Oxirgi 2026-10-23,USD flush da chiqdi. chiqar yordamchisi ikki joyda bir xil qoidani ishlatadi — transform va flush farq qilib qolmasligi uchun.

4-mashq: CLI qadami — matnga generatori

Bu dars holat 35 ning bir qismi; hali commit qilmang — commit katta fayl amaliyoti darsida. src/tarix.ts ga matnga(kurslar, format) async generatorini qo'shing. U Kurs obyektlari oqimini matn bo'laklariga aylantirsin: csv — sarlavha + qatorlar (csvQator), json — [ … ] bo'laklab (bo'sh oqimda ham yaroqli), jadval — sana bilan jadval qatori. Keyin oqimniYoz(matnga(...), ...) (oldingi dars) uni Readable.from bilan oqimga aylantiradi.

Yechim

Kanondagi kod:

ts
export async function* matnga(
  kurslar: AsyncIterable<Kurs>,
  format: Format,
): AsyncGenerator<string> {
  if (format === "csv") yield CSV_SARLAVHA + "\n";
  if (format === "json") yield "[";
  let birinchi = true;
  for await (const k of kurslar) {
    if (format === "csv") yield csvQator(k) + "\n";
    else if (format === "json")
      yield (birinchi ? "\n  " : ",\n  ") + JSON.stringify(k);
    else yield `${k.sana}  ${jadval([k], false)}\n`;
    birinchi = false;
  }
  if (format === "json") yield birinchi ? "]\n" : "\n]\n";
}

(Kanonda imzo bitta qatorda; telefonga sig'ishi uchun bo'lindi.) «Butun konveyer» bo'limidagi jsonMassiv bilan solishtiring: g'oya bir xil, faqat flush o'rnida sikldan keyingi yield. Kanon testi bo'sh oqimni tekshiradi: matnga(Readable.from([]), "json") → JSON.parse → [].

9. Real ishda

  • Ma'lumot import/eksporti: CSV → bazaga, bazadan → Excel, log → JSON. Har birida o'rtada bitta-ikkita Transform turadi. Tayyor paketlar ham shu asosda: csv-parse (CSV o'qish) Transform qaytaradi.
  • Kutubxonalar ichida: zlib.createGzip(), crypto.createHash() oqim shakli — Node'ning o'z Transform'lari. Ularni ishlatganda siz bugungi _transform/_flush ni bilvosita chaqirasiz.
  • Generatorlar tendensiyasi: yangi Node kodida pipeline + async generator tobora ko'p. Klassli Transform'larni asosan eski kodda va kutubxonalarda uchratasiz.
  • Intervyu savollari: "Transform va Duplex farqi?", "_flush nima uchun kerak?", "objectMode da highWaterMark nimani sanaydi?".

Xulosa

  • Readable: read() ichida this.push(bo'lak), oxirida this.push(null).
  • Transform: transform(bolak, _kod, tayyor) — har bo'lak; flush(tayyor) — oxirida bir marta.
  • tayyor(null, x) — chiqar, tayyor() — tashla, tayyor(xato) — to'xtat; har yo'lda aynan bir marta.
  • objectMode — baytlar o'rniga JS qiymatlari; highWaterMark obyektlar sonini sanaydi.
  • Readable.from(generator) va pipeline ichidagi async generator — ko'p holatda eng qisqa va xavfsiz yo'l.

Keyingi dars: Async iteratorlar va Web Streams — oqimni for await bilan o'qish, Node va brauzer oqimlari orasida ko'prik (Readable.toWeb), TextDecoderStream va compose.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Node.js Transform stream yozish: o'z Readable va Transform oqimingiz — IlmHamroh